工作知识学习及总结系列文档
本人新入职场,菜鸟一枚,嵌入式也属于摸索中学习,所幸遇到很多友善的同事带我入门。本文仅记录第一次参与UART硬件调试的过程中遇到的问题及解决方案,虽然最后发现是因为一个令人无语的原因,但是在这个过程中我学到了很多~
由于我们项目需要MCU与模组通过UART通信,如下图所示,

我需要编写一个小程序,通过UART向模组发送AT指令,并把模组返回的响应保存到数组中打印出来。然而出师未捷,不清楚是MCU这边没有把消息发出去,还是MCU这边没有接收到模组的反馈,总之无法收到反馈信息。
起初我以为是我程序写的有问题,添加延时等都无效。导师看后觉得没啥问题,因为就只是一个非常简单的小程序。UART也只有TXD,RXD,以及时钟线。按理说应该不会出现什么问题的。导师开始建议我通过观察keil中DEBUG时的寄存器值,来观察是否有数据写入相应的 RX Shift Register等寄存器,MCU的UART框图如下所示:

然而从寄存器上看不出来任何变化,简直是没有变化也没有现象。由于板子是我们同事自己画图找工厂做的,无奈之下开始去实验室找硬件工程师帮忙一起找问题(看看到底是软件还是硬件问题)。
后面还找了一个有经验的同事帮忙调试。
首先改写程序为while(1)无限循环写数据,先不读数据,接出模组的TXD和RXD端口连接示波器,keil程序运行写数据时观察不到波形变换,后用一个串口连接器单独连接模组与PC端,串口连接器长这样:

连接串口之后,在电脑端打开串口助手工具,用串口助手调试,发现模组可以正常收发AT指令,说明模组没问题啊,可以正常接收AT指令也可以反馈响应。
之后就用串口连接器作为第三方来监听,用PC端程序发AT指令,用串口调试助手检测发送的数据(串口连接器连接MCU的RXD),发现没有AT指令发送出来。之后换了TXRX管脚,发现是可以发送数据的。于是导师帮忙重新检查了程序代码,发现我配置UART对应的TXRX管脚PD0和PD1的时候,程序中GPD写成了GPC忘记改正过来。改正之后,终于可以发送数据了。我还是不够谨慎细心啊,居然犯了这样的错误。羞愧(っ◞‸◟c)
之后我们就开始测试PC端能否接收到模组响应的数据,但是收不到响应,同事帮忙更改了程序,连接串口连接器试了一下,也不行。临近下班就作罢,第二天继续…
第二天硬件工程师请假,于是又修改了一下程序,看了一下时钟相关的程序。
第三天继续查找问题!
上午:
1,首先硬件上找工程师同事断开了MCU与模组之间的线路,将模组连接PC,用串口助手发送AT指令,收到了正确回复。
2,之后MCU连接电脑PC端,用串口助手发AT指令,电脑Keil跑程序读MCU串口数据,DEBUG时可以收到与串口助手发送的AT指令一模一样的指令。
3,断开板子上MCU与模组,连接MCU与样品模组,接收到无意义乱码(8,9,3,1…),之后接收到了ERR1(程序While(1)循环里面用的是接收字符的READ函数,用接收数组的Read函数则不行)

后面又收不到数据了
4,给MCU厂家打电话。那边说让测试串口波特率。发送0x55。测试波形。测得一格时间 104微秒,波特率约等于9600,和程序设置的波特率符合。(程序设置发送数组data[32],然后memset(data,0x55,sizeof(data)),循环发送data字符串,测量波特率)

5,PC连接模组,用串口助手发送连续的0x55,测量PC发出来的是9600波特率
6,模组通过串口连接线连接PC,用MCU发,模组收,模组收到的数据用串口工具在PC端打印,发现接收不到响应。测量发现MCU发送数据成功,但是模组好像没有收到,也不知道是收到了解析格式不对所以没有回应。
注:keil程序编写的程序会下载到MCU中,MCU连接模组。所以想要看到模组这边的收发情况需要借助串口连接线和电脑上的串口助手小工具。
下午:
1,拔掉ULINK烧录器,串口连接器连接板子,打开串口助手,发现串口助手收到了回复,正是刚才拷进去的程序收到的正确回应。(说明程序在自己跑,然后收到了回复),但是安上烧录器,用KEIL跑程序,串口助手就收不到回复了。
此步骤是想用串口连接器连接板子。用电脑PC端向MCU写入程序,MCU发指令,模组接受指令并回复,但是回复的时候不返回程序窗口,而是用串口助手去监测。
2,又试了一下上面的步骤。又没有现象了
3,无意中拔下了ULINK烧录器。发现程序在板子里面继续跑,而且正确的收到了模组的回应。后面经过一番反复调试。换成之前新唐样片的ULINK_PRO,就好使了。可以收到响应了。
4,后发现了新的问题,响应速度太慢。发送两万条指令,接收一个响应。。感觉是Read函数里面的一个延时有问题,同事建议在给的例程里面改写程序。
5,找了一个MCU厂家提供的UART TXRX中断例程修改程序。时钟没有改。改了波特率和串口配置。发现可以读写AT指令,但是读的时候会分两个包接收响应。
1,关于分包接收。用串口助手发送端(蓝线)连接板子PC2口(第四个端口)接收端,然后用串口助手发送一条较长的AT指令,用程序接收,结果也分了三个包接收的。说明是MCU接收这块儿有问题。
2,改了一下程序,中断中添加了判断AT字符串结尾的程序语句
if((u8InChar != '\n') & (flag == 0))
{
RecData[indexRec] = u8InChar;
indexRec ++;
}
if(u8InChar == '\n')
{
indexRec = 0;
flag = 1;
}
在中断while(UART_IS_RX_READY(UART3))
循环外打印RecData数组。可以打印完整接收数据。
完整的中断程序如下,我们目前是在主程序里无限循环写AT指令,在中断里面读取AT指令的响应。
void UART_TEST_HANDLE(void)
{
uint8_t u8InChar = 0xFF;
uint32_t u32IntSts = UART3->INTSTS;
int count1 = 0;
/* Receive Data Available Interrupt Handle */
if(u32IntSts & UART_INTSTS_RDAINT_Msk)
{
printf("\nInput:");
countRx ++;
printf("countRx is %d\n",countRx);
/* Get all the input characters */
while(UART_IS_RX_READY(UART3))
{
/* Receive Line Status Error Handle */
if(u32IntSts & UART_INTSTS_RLSINT_Msk)
{
/* Clear Receive Line Status Interrupt */
UART_ClearIntFlag(UART3, UART_INTSTS_RLSINT_Msk);
}
/* Get the character from UART Buffer */
u8InChar = UART_READ(UART3);
count1 ++;
//printf("Rec num is:%d\n",count1);
printf("%c ", u8InChar);
/* Check if buffer full */
if(g_u32comRbytes < RXBUFSIZE)
{
/* Enqueue the character */
g_u8RecData[g_u32comRtail] = u8InChar;
g_u32comRtail = (g_u32comRtail == (RXBUFSIZE - 1)) ? 0 : (g_u32comRtail + 1);
g_u32comRbytes++;
}
if(u8InChar != '\n')
{
RecData[indexRec] = u8InChar;
indexRec ++;
}
if(u8InChar == '\n')
{
printf("\nRecieve datas: %s\n",RecData);
indexRec = 0;
}
//printf("\nRecieve datas: %s\n",RecData);
//printf("\nTransmission Test: %s\n",g_u8RecData);
}
/* Buffer Error Interrupt Handle */
if(u32IntSts & UART_INTSTS_BUFERRINT_Msk)
{
/* Clear Buffer Error Interrupt */
UART_ClearIntFlag(UART3, UART_INTSTS_BUFERRINT_Msk);
}
}
在我无意中的尝试下,发现了新的问题:在TXRX程序里面,在读取READ语句后添加一行printf语句,接收数据包就从两个变成了一个。颇感神奇,难以理解。后面一个资深同事说可能是因为中断响应比发送要快,所以添加Printf语句后,可能刚好延时了一下。使得接收和发送同步了。
由于我们不想采用中断接收AT指令,在中断这边也没有继续深究。之后的工作是在主程序里面收发AT指令,通过判断寄存器来判断收发完成标志。然鹅,又发现了新的问题,在主程序里面接收会丢包!!!而用串口助手监测发现MCU是收到了完整数据的。所以就是我程序的问题了…
主程序接收不到完整数据这个问题困扰了我许久。
尝试了各种方法,起初就是加延时啊,修改程序结构啊之类的,反复尝试,屡屡失败。
后面发现每次总是接收到16字节的数据,这就奇怪了。我开始怀疑是不是主程序不如中断那样响应快,所以产生了丢包,而且UART的FIFO就是16字节大小的,于是,我猜测是因为数据来了以后,FIFO保存了前十六字节,在读取完这十六字节之后,剩下的数据没有保存进来或者在读取前十六字节的过程中,无法保存后续字节导致丢失。
老老实实的去重新阅读了MCU技术手册UART部分,仔细阅读了其中每一个寄存器的具体描述。进行了接下来的尝试…
疑问与尝试:
1,在FIFO读取的过程中,不清楚读取之后的字节会不会被清除,不过不管会不会被清除,依照FIFO先进先出的特点,后面的位也不会前移,之后到来的字节也无法放在读完留下来的空位上,所以只能等这一次十六个字节读完之后,才能再次储存数据。按理说读完之后再来一次新数据的话,应该自动清空FIFO并保存到FIFO中,但是收到的AT指令一次性到来21个字节,剩下的那些字节不知道会不会在读完前十六个字节之后保存进来。
编程尝试了在读取十六个字节之后,等待FIFO再次不为空,再读取一次,但是并没有读到剩下的字节,反而读到了一些 + 号。
2,如果是因为FIFO满的话,剩下的字节无法保存进来,那么如果在读完前十六个字节之后,就人为的清空FIFO,清空之后再去监测FIFO是否非空,在有数据的时候继续读取接下来的字节。
编程尝试:利用资料中的RX FIFO指针,当读完之后,该指针值应该为0,且这时FIFO非空,判断这个条件,满足的话就利用资料中的 RXRST 清空FIFO,清空之后,等待FIFO存数据,之后开始读数据,直到读到 ‘\n’退出本次读取。
但是结果没有达到期望,尝试修改了程序和循环条件,都没有效果。


3,其余尝试:
(1)先读后写(无效)
(2)添加人为BUFFER区域,保存多个数据(无效)
(3)修改UART_Read读取数组函数,发现该函数只是添加了一个接收超时自动退出的功能,也是一位一位循环读取 DAT中的字节,而且容易卡死在里面。(无效)
在读之后添加了一个计数变量reccount,读一次加1,然后读取循环条件变为while((UART_GET_RX_EMPTY(UART3) == 0) && (reccount < 22)),发现了奇怪现象,可以接收到一次完整数据,但是无法保存到数组中。之后又乱码了。
无法解决的问题不能自己硬干啊,咨询了导师。导师说我的方向偏了,之前各种查找资料好像都做了无用功呜呜呜。
最关键的问题是,我的程序中读取UART收到的数据时,循环条件是判断while(UART_IS_RX_READY(UART3) == 1),那么由于FIFO只有16个字节的缓存区,所以第一个16个字节数据到来之后,我读取到了,但是剩下的数据到来之前,可能有一个时间间隔,而程序在这时候不满足循环条件已经退出循环了,所以就收不到后续的数据。
而我更改的下面的程序也收不到完整的数据包,
while(1)
{
while (UART_IS_TX_EMPTY(UART3) == 0);
num = UART_Write(UART3, &g_u8SendData[0], g_sDatalen);
txcount ++;
printf("num is %d,txcount is %d\n",num,txcount);
//while(UART_IS_RX_READY(UART3) == 0);
while(UART_GET_RX_EMPTY(UART3) );
do
{
u8InChar = UART_READ(UART3);
printf("%c ", u8InChar);
g_u8RecvData[index ++] = u8InChar;
if(u8InChar == '\n')
{
rxcount ++;
printf("Our Received datas are: \n%s\n rxcount is %d\n",g_u8RecvData,rxcount);
}
}while(u8InChar != '\n');
//CLK_SysTickLongDelay(5000000);
//printf("Our Received datas are: \n%s",g_u8RecvData);
index = 0;
CLK_SysTickLongDelay(5000000);
}
会收到这样的结果
rxcount is 2
num is 9,txcount is 3
0 0 0 0 0 0 0 M I D = 0 0 0 0 0 0 0 0 0 0 0 1 , 1
Our Received datas are:
+0000000MID=000000000001,1
MID前面多了一串0,怀疑是好几个数据包混在一起了。
最后的解决方案是,要保证收到全部数据之后再结束读取操作,而且为了数据完整性,还是得判断FIFO是否有数据到来,再开始读取FIFO。修改后的程序为:
while(1)
{
while (UART_IS_TX_EMPTY(UART3) == 0);
num = UART_Write(UART3, &g_u8SendData[0], g_sDatalen);
txcount ++;
printf("num is %d,txcount is %d\n",num,txcount);
//while(UART_IS_RX_READY(UART3) == 0);
#if 1
while(1)
{
while(UART_GET_RX_EMPTY(UART3));
while(UART_GET_RX_EMPTY(UART3)==0)
{
u8InChar = UART_READ(UART3);
//printf("%c ", u8InChar);
g_u8RecvData[index ++] = u8InChar;
}
if(u8InChar == '\n')
{
rxcount ++;
printf("Our Received datas are: \n%s\n .index is %d.\n,rxcount is %d\n",g_u8RecvData,index,rxcount);
index = 0;
break;
}
};
//printf("Our Received datas are: \n%s",g_u8RecvData);
index = 0;
#endif
CLK_SysTickLongDelay(5000000);
}
虽然while 套 while 有点奇怪,不够这个也只是测试无中断主程序UART能否完整收到数据的测试程序。之后也不能无限循环收发数据。但是这个起码是一次成功的尝试,多日来的问题终于初步解决了!
调试UART暂时告一段落。虽然走了很多弯路,因为用错ULIINK型号导致出现问题也是令人啼笑皆非。起初厂家寄来的样品中附带的ULINK是适配的,后面因为我们还需要一个ULINK,而厂家寄过来需要好久,所以同事在天猫买了一个,为了便宜买了错误的型号(虽然天猫客服信誓旦旦的说可以用,厂家那边说应该可以用),导致了后面将近一周的硬件调试。不过我在这个过程中学到了很多,调试过程中反复焊接,经常是测一段时间发现有问题,又开始从头再来,同样的步骤反复操作,实在是颇费耐心,大佬同事表示,硬件调试就是要反反复复进行,同时也要提前猜测问题然后去验证猜想排除问题,不能深陷于过程中,被结果误导偏离方向。
之后我自己调试程序也发现了这一点的正确性。跟着结果去修改程序效率很低,最关键的是要先找到核心问题,之后再去猜想实践验证。很开心学到了新知识,也开始慢慢入门嵌入式啦,往后还要继续努力~