问题发现
今天在实现STM8S的UART收发时,出现这样一种情况,如图1:
图1
代码功能是通过UART接收一个字符,字符自增一,然后输出;
所以按照逻辑,代码执行顺序应该是:
PC端通过Tx发送字符 -> STM8S端通过Rx接收字符,字符自增一 -> STM8S端通过Tx发送字符 -> PC端通过Rx接收字符
所以图1中PC端还没发送字符,STM8S端就已经发送字符过来了,这显然不符合我们的设计思路
解决思路
毫无疑问,从主函数main()入手,主函数的主要代码部分为下图:
图2
可见,代码实现符合我们的收发逻辑,现在目光转到函数UART_RX()的定义处
图3
现在我们看到while(!(UART1_SR && (1<<5)));
语句,该语句功能用文字可描述为: 检查UART1_SR寄存器的bit 5是否为1,如为1则跳出循环,0则继续循环 。
此时罪魁祸首出现了,即&&
逻辑与运算符,将&&
改为&
(在 涉及知识点 部分有详细关于&
和&&
的异同),然后重新编译执行,结果如下:
图4
可见PC端的收发数据表现正常
涉及知识点
有些啰嗦,但顺便复习一下
while (表达式的真假(值))
{
代码块
}
while语句中括号 " () "里的应摆放一个表达式,而C的表达式具有以下属性:
- 运算符
- 运算对象
- 表达式的值
ATTENTION
-
最简单的表达式可以是一个单独的运算对象,即常量/变量
-
采用一般运算符的表达式,会得到“值 ”
-
采用 逻辑/关系 运算符的表达式,也会得到“值 ”,但是这个“值 ”通常被称为“真(true)/假(false) ” 。这个真假值 由 逻辑/关系 表达式的运算结果决定。非0数都为“真 ”,有且仅有0表示“假 ”(由1. 可知,一个非0的常量/变量,也可以表示真;非0数都为“真 ”同时常量0也表示“假 ”)
对于关系运算符有以下例子:
例1:
while (1 == 1)// 关系表达式1 == 1,有返回值1,1为非0数,while语句判定为真,进入循环体
{
// 代码块
}
例2:
while (1 > 2) // 关系表达式1 > 2,有返回值0,while语句判定为假,不进入循环体,执行循环体后的代码
{
// 代码块
}
对于逻辑运算符有以下例子:
例3:
int a = 1, b = 1;
while (a & b)
// 1. 逻辑表达式a & b,有返回值1,1为非0数,表达式值为真
// 2. while语句判定为真,执行循环体语句
{
// 代码块
}
例4:
int a = 2, b = 1, c = 0;
while ((a > b) && (b > c))
// 1. 关系表达式a > b成立,有返回值1,1为非0数,表达式值为真
// 2. 关系表达式b > c成立,有返回值1,1为非0数,表达式值为真
// 3. 即有while(真 && 真)
// 4. while语句判定为真,执行循环体语句
{
// 代码块
}
例5:
int a = 2, b = 1, c = 0;
while ((a < b) && (b > c))
// 1. 关系表达式a < b不成立,有返回值0,表达式值为假
// 2. &&运算符不执行右侧关系表达式(b > c)的运算,直接两个表达式的结果为假;
// 因为与运算中只要有一端的值为假,那么整个表达式的值为假
// 3. while表达式判定为假,执行循环体之后的语句
{
// 代码块
}
ATTENTION
从结合例3、例4、例5比对我们有以下结论:
对于&&
运算符在例4、例5的比较,有
- 当
&&
运算符左侧的运算对象为false时,则不会判断&&
右侧的运算对象 ,执行后面的语句
对于&
运算符和&&
运算符在例3、例4的比较,有
&
直接取两端的表达式进行与运算,返回一个值,根据该值运算的值判断真假&
运算符在已知左侧运算对象结果为false的情况,仍然会计算其右侧的运算对象,因为需要知道&
两端的真值来进行与运算&&
运算符参与的逻辑表达式,则要求两端表达式的值均为真即表示&&
表达式结果为真,不拿两端的表达式做与运算- 当
&&
左侧表达式真值为假,直接判定为假,执行后续语句
这样就能解释为什么while(!(UART1_SR & (1<<5)));
能正常收发数据,分析如下:
UART1_SR有默认值0xC0 = 0b1100 0000,位移表达式有值 (1<<5) = 0b0010 0000 ,两表达式与运算结果为false,!(false)为真,while一直执行空循环,则需等条件满足才能跳出循环
while(!(UART1_SR && (1<<5)));
却会导致PC端还没发送数据,STM8S端就已经有数据发送过来了,分析如下:
UART1_SR有默认值0xC0 = 0b1100 0000 恒为真,位移表达式有值 (1<<5) = 0b0010 0000 恒为真,由上述例子结论
&&
则要求两端表达式的值均为真即可,不拿两端的表达式做与运算
可知,while(!(UART1_SR && (1<<5)));
有while(!(true))
,表示恒为假,没有执行执行空循环的循环体,直接职执行后面的语句
关于&
和&&
的使用时机
- 利用
&&
的机制我们可以把它用在判断语句中,这样效率更高,既然知道&&
前表达式结果为假,那么就不需要计算后面的表达式了 - 对于
&
则老老实实用于表达式的与运算中,求两端表达式值的与值
关于 |
与 ||
运算符
- 或运算符和与运算符一直,当为
||
时,左侧表达式为真,则直接返回真,不去运算右侧表达式的值 - 为
|
时则两侧都作运算,因为需要两端的值来做或运算
补充
当while(!(UART1_SR && (1<<5)));
时,程序详细发生了什么
从 涉及知识点 我们得知了&&
的运行机制,不妨分析此时程序发生了什么
- 首先,查看参考手册,可知UART1_SR的默认值为0xC0
- 因为UART1_SR为非0值,所以恒为真;(1<<5) 表达式的值也为非0,所以也恒为真;此时回到表达式
UART1_SR && (1<<5)
,由于两侧表达式结果恒为真, 根据&&
的特性,UART1_SR && (1<<5) 表达式的返回值恒为真 - 不妨将 UART1_SR && (1<<5) 设为true,则表达式
!(UART1_SR && (1<<5))
等价于!(true)
,!(true)
又等价于false
- 将
false
带回语句while(false);
false表示值为0,while语句跳过不执行循环体,执行循环体下一条语句return UART1_DR;
十分关键,这表示了,STM8S并没有收到任何来自PC端的输入,但是通过软件将UART1_DR的原始值输出了
所以UART的收发逻辑流程从
PC端通过Tx发送字符 -> STM8S端通过Rx接收字符,字符自增一 -> STM8S端通过Tx发送字符 -> PC端通过Rx接收字符
变成了
STM8S端通过Tx以字符形式发送存放在UART1_DR的默认值 -> PC端通过Rx接收字符
而且因为不需要PC端输入,下面这个流程会一直持续!!!! 所以就解释了为什么会出现PC端Rx和Tx数据不对等的情况
总结
按理说,这是一个很简单的问题,但因为没有注意细节,导致了bug的出现,花费大量时间debug才发现是一个逻辑符号处理逻辑的问题,所以说基础是一切根本,其他技术都是基础的延伸