USART_案例(一)接收字符串代码改进

引言

       前面我们基本实现了第一个串口通讯案例,使得32芯片与计算机进行了数据的收发,包括了单字符的收发和字符串的收发。在我们实现字符串接收函数时,发现其接收字符串的实现不能完全按照之前发送字符串的思路直接轮询判断,所以对代码进行了修改,直接重新写了一遍获取单字符的代码,不过在其中循环等待判断可接收数据时多加了检测到空闲帧结束的语句,然后就基本实现了字符串接收,但出现了双重while循环的语句。如下图所示

       本次我们就来对上一次接收字符串函数做一下改进,看看是否能够直接单个循环接收字符来实现接收字符串,当然减少这里的循环,比如会在其他部分进行相应修改。毕竟有简化就避免不了其他地方的复杂,当然也没有多麻烦哈哈。

       当然,为避免大家可能没看过上一个案例相关内容,所以这里附上链接,直接跳转即可USART_串口通讯轮询案例(一)(寄存器实现)-优快云博客https://blog.youkuaiyun.com/2301_79475128/article/details/145222170?spm=1001.2014.3001.5501


一、状态位的置位和清除

       首先,我们先聊聊我们实现串口通讯案例时常用的几个状态位(TXE、RXNE、IDLE),因为大家可以发现,我们使用这些状态位主要都是来判断什么时候开始发数据和收数据的。也就是说,上一个案例实现的核心就在于这几个状态位的使用,所以这里我们就来稍微在解析一下这几个状态位的用法。

对于这几个状态位的使用,我们主要还是要去手册中理解关于他们的说明:

       这里先看看状态寄存器的复位值,也就是我们单片机刚上电时该寄存器默认的值,由上图可以发现是0x00C0,换成二进制就是0000 0000 01100 0000,对应在寄存器的位上意味着TXE默认置1、TC默认置1、RXNE默认清零、IDLE默认清零。


1.1 TXE位

我们在参考手册中找到对于该位的描述,如上图

        根据上图说明我们容易知道TXE表示的是发送数据寄存器为空的标志,当数据寄存器中的数据被转移到移位寄存器时就会被置1,当我们再次写入数据的时候,该位又会清零。这两个操作都是由底层硬件自动操作。

同时我们知道,TXE位在刚上电的时候默认会置1,这是为什么呢?

       根据上面的理解,TXE为1是指此时发送的数据为空,可以开始下一轮数据的发送。而我们刚上电时,当然是相当于没有发送数据嘛,也就是即将开始下一轮数据的发送,所以可以认为是发送数据已经为空,因此默认就置了1。


1.2 TC位

TC位在手册中的描述如上图,该位我们没有使用,因为TXE可以起到差不多的效果

       根据上图我们可以知道,TC位表示的是一帧数据发送完成后并且TXE位为1的标志,也就是一个数据从数据寄存器转移到移位寄存器并且被发送到外部以后会被硬件自动置1,然后我们可以通过软件先读取状态寄存器SR、再读取数据寄存器来将TC为清零。

同时我们知道,TC位在刚上电时会默认置1,这是为什么呢?

       根据上面的理解,TC为1是指数据发送完成了,可以开始让下一轮的数据开始发了。而我们刚上电时,就是准备开始发送数据,就相当于我们上一轮发送完毕了嘛,当然实际上还没有上一次哈哈。因此刚上电时就可以默认为上一次发送完毕,可以开始下一轮数据发送了,故默认置为1


1.3 RXNE位

关于RXNE位在手册中的描述如上图

       根据上图我们能够知道,RXNE表示的是接收的数据不是空的标志位。当移位寄存器的数据转移到数据寄存器中时,接收的数据便不是空了,此时硬件会自动将该位置1;当我们读取数据寄存器中的数据后,该位会被硬件自动清零。

同时,我们知道,RXNE位在刚上电时会默认清零,这是为什么呢?

       由上述理解可知,RXNE为0是指接收的数据为空,不用读取下一轮数据。我们刚上电时,没有发送数据也没有接收数据,就相当于咱可接收的数据为空,因此默认认为不用接收数据,即接收的数据默认为空,所以置0了


1.4 IDLE位

关于IDLE位在手册中的描述如上图

       根据上图可知,IDLE表示的是总线处于空闲,也就是检测到一段空闲帧的标志。当一段时间没有数据发出而产生一段空闲帧时,该位会被硬件自动置1;然后检测到后结束数据接收即可通过软件方式进行清零操作,即先读取状态寄存器,再读数据寄存器就可以。

       同样,我们知道,IDLE位刚上电时会默认清零,但是该状态位本来是为了去检测空闲帧以便判断变长数据接收完毕的标志,为0就相当于刚上电时不是处于连续高电平。但我们思考,刚上电时相当于前面已经发送完毕接受完毕了,按理说应该就相当于是处于连续高电平,那么为什么这时候他默认不是已经检测到空闲帧呢?

       实际上是因为IDLE状态位设计出来的本意就是为了去检测我们接收不定长数据完毕之后的空闲帧而产生的一种判断结束标志,所以他只会在我们发送完毕以后产生的空闲帧,也就是出现一段连续高电平的时候才会被置为1。因此他默认就不是1而是0


二、衍生的改进思路

        根据以上分析呢,我们可以发现这几个状态位中最不一样的就是IDLE空闲标志位了。而实际上,我们前面案例实现接收字符串函数时遇到的问题也是和这个东西有关,后面对代码的改进也主要是利用了IDLE这个位作为防止循环卡死程序的标志位。

       换句话说,这次我们代码的改进思路实际上主要也是由这个状态位衍生出来的,然后结合RXNE位进行了接收字符串函数的代码改进

       因此呢,我们可能会有这样的思路来编写代码实现接收字符串:

       IDLE位默认为0,所以我们就直接循环检测空闲帧去,然后为1就说明我们可以结束接收数据,完成字符串接收了。

       然后循环检测空闲帧的同时,顺便存一下未出现空闲帧 并且没有完成接收时 接收到的每一个字符

       对于接收单字符函数来说,此时我们可以在没有可接收数据时的循环等待中加一个检测空闲帧来直接结束字符接收。

       这时候我一直循环去接收每一个字符时,若没有检测到可接收数据、然后一直不接收的话(这个时候其实已经意味着我们字符串的每一个字符已经接受完毕),则会一直循环等待,同时一直没有接收到数据而出现一段空闲帧了的话,这个循环等待结束的标志就会变成检测到空闲帧结束接收字符,这时循环检测空闲帧也必然能检测到一段空闲帧。此时就意味着我们字符串已经接受完毕,即实现了这完整的发送字符串数据的函数。

以上就是我们对利用双层循环重新检测这一思路的改进思路

       值得注意的是,IDLE位不会自动清零,需要用特定的方式进行软件清零。所以我们每一次接受完字符串数据以后要进行清零操作,具体方式可以看看参考手册说明,即读取状态寄存器,然后再读取数据寄存器即可。

       (自己的理解)实际上主要就是为了再次确认这个状态是什么样,然后数据是不是真的没有,这样才能保证我此时已经完成数据的接收没有在进行了,那么此时我就可以相当于回到默认的零状态了。

根据以上思路进行代码编写,这里参考的代码如下

// 接收一个字符
uint8_t USART_ReceiveChar(void)
{
    // 当接收端为空时等待
    while ((USART1->SR & USART_SR_RXNE) == 0)
    {
        // 若一直等待时检测到空闲帧则直接结束
        if(USART1->SR & USART_SR_IDLE)
        {
            return 0;
        }
    }

    // 接收一个字符
    return USART1->DR; 
}
// // 接收一个字符串
void USART_ReceiveString(uint8_t buffer[], uint8_t *size)
{
    uint8_t i = 0;
    // 没有检测到空闲帧时记录字符,检测到就结束
    while ((USART1->SR & USART_SR_IDLE) == 0)
    {
        buffer[i] = USART_ReceiveChar();
        i++;
    }
    
    // 清除IDLE位  读状态寄存器,再读数据寄存器即可
    USART1->DR;     // 因为最后一次循环已经读取过SR,所以这里只需要读取DR

    *size = --i;
}

注意:这里最后我们让接收到的字符长度-1,是因为接收字符串时最后产生空闲帧结束字符接收会再多返回一个0,在字符串中就是“\0”,但是这不是我们希望收到的字符,所以我们干脆长度-1,这样就不会显示出来,在逻辑上我们就相当于没有接收到额外的字符了,这样也更符合我们接收的内容。

       话不多说,我们使用这个改进代码去试试,在main.c中实现接收字符串并发送的效果来进行测试。其中main.c中测试代码如下

#include "usart.h"

// 全局变量 存放接收到的字符串
uint8_t buffer[100] = {0};
uint8_t size = 0;

int main(void)
{
	// 初始化
	USART_Init();

	// 4. 死循环保持状态
	while(1)
	{		
		USART_ReceiveString(buffer, &size);
		USART_SendString(buffer, size);
	}
}

编译一下

没有问题,我们直接烧录在串口助手试试

显然,可以合理实现我们字符串数据的收发功能。


OK,本次字符串接收函数的代码改进就到此为止,谢谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值