【STM32H7S78-DK】测评UART+DMA之二
<div class='showpostmsg'><p>【前言】</p><p>在上一篇,我分享了串口+DMA不定长的接收:</p>
<p><a href="https://bbs.eeworld.com.cn/thread-1295339-1-1.html">【STM32H7S78-DK】测评+TouchGFX UART+DMA不定长接收 - stm32/stm8 - 电子工程世界-论坛 (eeworld.com.cn)</a></p>
<p>经调试发现了一些问题,经仔细的调试发现,串口触发了三个中断,一是DMA的接收到一半的中断,另一个是接收到DMA缓冲区满的中断,再有一个就是串口空闲中断。而我们需要使用到的,只需要串口空闲中断,在别两个中断中,会触发接收字符串中断,不是我需要的目的。为此找到解决方法如下。</p>
<p>【步骤1】</p>
<p>首先修改串口接收的缓冲区,由于我们的STM32H7S78-DK内存够用,所以拿出2K来做接收缓冲区。同时跟上篇一样,指定存放到GPDMA可以正常访问的地址上。</p>
<pre>
<code>#define U4_RX_SIZE 2048
#define U4_RX_MAX 256
#define U4_RX_NUM 10
/* 用于存放UART4的接收缓冲区 */
uint8_t U4_RxBuff __attribute__((section("noncacheable_buffer")));;
//指定内存使用地址:
RW_NONCACHEABLEBUFFER0x24072000-0x400 0x400{
*(.noncacheable_buffer)
}</code></pre>
<p>【步骤2】</p>
<p>由于我们使用到TouchGFX,需要使用大量时间来处理图形,如果大量的数据到来,处理数据需要一定的缓冲,因此需要使用缓冲来记录发来的数据。</p>
<p>用手画了一个草图:</p>
<p> 创建两个结构体,来存放我们处理数据:</p>
<pre>
<code>/* 用于存放一帧数据指向的起始地址,与结束地地 */
typedef struct{
uint8_t *start;
uint8_t *end;
}UCB_URxBuffptr;
/*URxCounte:用于存放当前已接收到的数据的个数,
UCB_URxBuffptr:存放起始址的环形数组
URxDataIN:记录当前接收缓冲区所在的地址
URxDataOUT:处理数据所地的地址
URxDataEND:环形缓冲区的最大地址。
*/
typedef struct{
uint16_t URxCounter;
UCB_URxBuffptr URxDataPtr;
UCB_URxBuffptr *URxDataIN;
UCB_URxBuffptr *URxDataOUT;
UCB_URxBuffptr *URxDataEND;
}UCB_CB;</code></pre>
<p>初始化结构体:</p>
<pre>
<code>/* USER CODE BEGIN UART4_MspInit 1 */
__HAL_DMA_DISABLE_IT(&handle_GPDMA1_Channel0, DMA_IT_HT);
memset((void *)show_buff, 0U, sizeof(show_buff)); /* 清理结构体 */
UCB_UART4.URxDataOUT->start = &U4_RxBuff; /* 将接收初始体指定接收缓冲区地始地址 */
UCB_UART4.URxDataIN ->start = &U4_RxBuff; /* 将处理初始体指定接收缓冲区地始地址 */
UCB_UART4.URxDataEND = &UCB_UART4.URxDataPtr +1; /* 将结束地址设置为最大数组+1的地址,到达此地址后,回环到原始地址 */
UCB_UART4.URxCounter = 0; /* 计数器清零 */
/* USER CODE END UART4_MspInit 1 */</code></pre>
<p>接收控制先清零,把接收地址的start与数据处理地址start指向DMA接收的首地址。(如果不这样处理,处理第个缓冲区时,由于start为0,与end指针地址计算时会误认为处理一个uint32的数据。</p>
<p>【步聚3】</p>
<p>编写中断回调函数:</p>
<pre>
<code>void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
UCB_UART4.URxCounter = Size; /*把计数器更新到DMA缓冲区计数据器*/
UCB_UART4.URxDataIN->end = &U4_RxBuff; /*接收结束址址指向缓冲区的计数器减1 即如果第一次接收到100个,那计数值为0-99*/
UCB_UART4.URxDataIN ++; /*接收环形地址增1,为下次数据接收做准备*/
if(UCB_UART4.URxDataIN == UCB_UART4.URxDataEND){ /*如果到达结束地址 回滚到起始地址*/
UCB_UART4.URxDataIN = &UCB_UART4.URxDataPtr;
}
/*判断可用缓冲区是否满足下一次最大接收量,
如果满足更新指钟到计数据当前值
如果不满足则回滚到缓冲区的首地址
*/
if(U4_RX_SIZE - UCB_UART4.URxCounter >= U4_RX_MAX)
{
UCB_UART4.URxDataIN->start = &U4_RxBuff;
}
else
{
UCB_UART4.URxDataIN->start = U4_RxBuff;
UCB_UART4.URxCounter = 0;
/* 停止DMA*/
HAL_UART_DMAStop(&huart4);
/* 重新开始接收 */
if (HAL_OK != HAL_UARTEx_ReceiveToIdle_DMA(&huart4, U4_RxBuff, U4_RX_SIZE))
{
Error_Handler();
}
/* 关闭DMA达到传输一半数据后的中断 */
__HAL_DMA_DISABLE_IT(&handle_GPDMA1_Channel0, DMA_IT_HT);
}
}</code></pre>
<p>在中断处理函数中处理数据流程:</p>
<p>1、把计数器更新到DMA缓冲区计数据器。</p>
<p>2、接收结束址址指向缓冲区的计数器减1 即如果第一次接收到100个,那计数值为0-99</p>
<p>3、接收环形地址增1,为下次数据接收做准备。如果到达结束地址 回滚到起始地址。</p>
<p>4、判断可用缓冲区是否满足下一次最大接收量,如果满足更新指钟到计数据当前值</p>
<p>如果不满足则回滚到缓冲区的首地址,停止DMA,再重新启用DMA传输,主要是复位计数器,以及接缓缓冲区首地址。然后关闭DMA接到一半的中断。</p>
<p>这样处理主要是避免产生半中断、DMA满中断。只需要串口接收空闲中断,这样就可以处理一帧完整的数据。</p>
<p>【测试】</p>
<p>在一个任务中,添加测试代码:</p>
<p>1、先计算接收与处理的两个指针是否为相等,如果不相等则将接收到的数据从串口回传:</p>
<p>2、处理好后,将处理指钟自增,如果到达END地址,回滚到首地址。</p>
<p>代码如下:</p>
<pre>
<code> if(UCB_UART4.URxDataOUT != UCB_UART4.URxDataIN)
{
printf("本次接收到了%d字节数据\r\n",UCB_UART4.URxDataOUT->end - UCB_UART4.URxDataOUT->start +1);
for(i=0;i<UCB_UART4.URxDataOUT->end - UCB_UART4.URxDataOUT->start +1;i++)
{
printf("%c",UCB_UART4.URxDataOUT->start);
}
printf("\r\n");
UCB_UART4.URxDataOUT ++;
if(UCB_UART4.URxDataOUT == UCB_UART4.URxDataEND)
{
UCB_UART4.URxDataOUT = &UCB_UART4.URxDataPtr;
}</code></pre>
<p>实验效果:</p>
<p> </p>
<p>【分析】</p>
<p>在工程中,我使用了HAL_UARTEx_ReceiveToIdle_DMA 函数来启用串口DMA传输。这个函数,在示例中有解释:</p>
<pre>
<code>/* Initializes Rx sequence using Reception To Idle event API.
As DMA channel associated to UART Rx is configured as Circular,
reception is endless.
If reception has to be stopped, call to HAL_UART_AbortReceive() could be used.
Use of HAL_UARTEx_ReceiveToIdle_DMA service, will generate calls to
user defined HAL_UARTEx_RxEventCallback callback for each occurrence of
following events :
- DMA RX Half Transfer event (HT)
- DMA RX Transfer Complete event (TC)
- IDLE event on UART Rx line (indicating a pause is UART reception flow)
*/</code></pre>
<p>意为中断会去处理HT,TC,IDLE两个中断,这与我们想要的结果不一样,经查看官方的函数,才找到解决方法,即在启用后,使用__HAL_DMA_DISABLE_IT(&handle_GPDMA1_Channel0, DMA_IT_HT); 为关闭HT事件,同时,也在即将到害缓冲区最大长度时,重启DMA,始终不会产生TC事件。这样就可以成功得到我们需要的中断回调函数了。</p>
<p>当HAL库给我们更大的方便的同时,如果需要实现自己的即定功能,也会带来一定的不便,为了接收到的数据帧的不完整排得了两天。如果想要深入用好一个MCU,对底层的寄存器还需要深一步的研究,仔细的查看用户手册。</p>
</div><script> var loginstr = '<div class="locked">查看本帖全部内容,请<a href="javascript:;" style="color:#e60000" class="loginf">登录</a>或者<a href="https://bbs.eeworld.com.cn/member.php?mod=register_eeworld.php&action=wechat" style="color:#e60000" target="_blank">注册</a></div>';
if(parseInt(discuz_uid)==0){
(function($){
var postHeight = getTextHeight(400);
$(".showpostmsg").html($(".showpostmsg").html());
$(".showpostmsg").after(loginstr);
$(".showpostmsg").css({height:postHeight,overflow:"hidden"});
})(jQuery);
} </script><script type="text/javascript">(function(d,c){var a=d.createElement("script"),m=d.getElementsByTagName("script"),eewurl="//counter.eeworld.com.cn/pv/count/";a.src=eewurl+c;m.parentNode.insertBefore(a,m)})(document,523)</script>
页:
[1]