硬汉嵌入式论坛

 找回密码
 立即注册
查看: 755|回复: 4
收起左侧

[有问必答] DMA在发送过程中,突然其他任务改变了发送的内容,造成数据错误,该如何避免?

[复制链接]

5

主题

30

回帖

45

积分

新手上路

积分
45
发表于 2022-9-22 16:32:35 | 显示全部楼层 |阅读模式
硬件平台:STM32F407开发平台:Keil MDK
问题现象:
我使用FreeRTOS 创建了2个任务。这两个任务中,都会对同一个全局变量数组进行修改。但是任务2只有按键按下了才会修改。任务1:启动串口DMA发送数据固定格式的“读数据帧”。并检测写标志是否置1,置1则发送写数据
[C] 纯文本查看 复制代码
void Master_SendPackagetoSubcotrol(void)
{
    static uint8_t s_Current_ID = 0;
    uint16_t crc_check = 0;

    if(g_ucRS485ReadWriteFlag)
    {    
        g_ucRS485ReadWriteFlag = 0;
    }
    else
    {
        g_RS422TxBuf[0] 	= 0x24;
        g_RS422TxBuf[1] 	= g_buffSubcontrolID[s_Current_ID];
        g_RS422TxBuf[2] 	= 0xFF;
        g_RS422TxBuf[3] 	= 0x03;
        g_RS422TxBuf[4] 	= 0x00;
        g_RS422TxBuf[5] 	= 0x36;
		g_RS422TxBuf[6] 	= sizeof(ModuleData_T);//数据长度 字节计算
        g_RS422TxBuf[7] 	= 10;
		
        s_Current_ID++;
        if(s_Current_ID >= SUBMODULE_NUM+1)s_Current_ID = 0;

        crc_check = 0x55AA;

        g_RS422TxBuf[g_RS422TxBuf[7]-2] = U16_LowByte(crc_check);
        g_RS422TxBuf[g_RS422TxBuf[7]-1] = U16_HighByte(crc_check);
    }

    //启动DMA传输
    if(DMA_GetFlagStatus(RS485_USART_DMA_STREAM, RS485_USART_DMA_TCIF_FLAG) != RESET)//等待DMA2_Steam7传输完成
    {
        DMA_ClearFlag(RS485_USART_DMA_STREAM, RS485_USART_DMA_TCIF_FLAG);//清除DMA2_Steam7传输完成标志
    }
    DMA_Enable(RS485_USART_DMA_STREAM, g_RS422TxBuf[7]);     //开始一次DMA传输!

}

任务2:按键按下了,修改全局变量数组,发送“写数据帧”标志置1。等待任务1中检测到标志后启动发送。
[C] 纯文本查看 复制代码
void delete_key_up(void)
{
	if(key_in() == 1)
   {
		g_ucRS485ReadWriteFlag = 1;
		g_RS422TxBuf[FRAME_INDEX_HEAD] 			= FRAME_HEAD;
		g_RS422TxBuf[FRAME_INDEX_SLAVE_ID] 		= 0xff;
		g_RS422TxBuf[FRAME_INDEX_DRIVER_ID] 	= 0xff;
		g_RS422TxBuf[FRAME_INDEX_OPERATE] 		= FRAME_OPERATE_WRITE;
		g_RS422TxBuf[FRAME_INDEX_ADDRESS_H] 	= 0;
		g_RS422TxBuf[FRAME_INDEX_ADDRESS_L] 	= CMD_SubCOntrol_Write_GuideControlStatus;//写地址

		g_RS422TxBuf[FRAME_INDEX_DATA_LENGTH] 	= 2;
		g_RS422TxBuf[FRAME_INDEX_FRAME_LENGTH] 	= g_RS422TxBuf[FRAME_INDEX_DATA_LENGTH] + 10;
	   

		*(uint16_t *)(g_RS422TxBuf+FRAME_INDEX_DATA_H) = 0xCCCC;
		crc_check = 0x55AA;

		g_RS422TxBuf[g_RS422TxBuf[FRAME_INDEX_FRAME_LENGTH]-2] = U16_LowByte(crc_check);
		g_RS422TxBuf[g_RS422TxBuf[FRAME_INDEX_FRAME_LENGTH]-1] = U16_HighByte(crc_check);
   }
}

然后问题就来了。如果任务1中DMA刚刚启动完,然后任务2就把按键按下,修改了DMA传输内存中的值,造成了DMA数据数据的错乱。如下图所示:

image-20220921170529067.png
其中,
24 06 FF 10 00 19 02 CC CC CC 就是传输错误,本来需要传输的是, 24 06 FF 03 00 36 00 0A AA 55,结果在传输的过程中被改变了导致错误。
那么在实际应用中该如何避免?
现在我的做法是,将检测按键的任务与发送数据的任务合为一个任务。并且,按键检测函数在前,发送函数在后可以避免该问题。
或者可以采用while()等待DMA发送完成,然后在执行任务调度,考虑到任务中采用死等的方式,会造成其他任务的不执行因此没有采用。

回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
107294
QQ
发表于 2022-9-22 17:21:57 | 显示全部楼层
你的DMA是Normal正常模式还循环模式,如果是正常模式,你可以读取DMA的NDTR寄存器剩余发送数据个数即可,完成了再修改就行。

如果是循环模式,可以使用DMA双缓冲,一个发送的时候,修改另一个缓冲。
回复

使用道具 举报

5

主题

30

回帖

45

积分

新手上路

积分
45
 楼主| 发表于 2022-9-22 18:40:01 | 显示全部楼层
eric2013 发表于 2022-9-22 17:21
你的DMA是Normal正常模式还循环模式,如果是正常模式,你可以读取DMA的NDTR寄存器剩余发送数据个数即可,完 ...

我使用的是串口发送DMA。采用的是noermal正常模式。感谢硬汉哥回复。我试试判断一下NDTR寄存器。。
回复

使用道具 举报

5

主题

192

回帖

212

积分

高级会员

积分
212
发表于 2022-9-24 11:00:20 | 显示全部楼层
xdh873939316 发表于 2022-9-22 18:40
我使用的是串口发送DMA。采用的是noermal正常模式。感谢硬汉哥回复。我试试判断一下NDTR寄存器。。

你可以用FIFO队列,或者在 TxCpltCallback里面做处理,没完成前等待
回复

使用道具 举报

0

主题

15

回帖

15

积分

新手上路

积分
15
发表于 2022-9-26 17:25:23 | 显示全部楼层
可以使用RTOS的事件标记,开始发送的时候清除事件标志,在DMA发送完成的中断中置位事件标志,在发送任务里等待事件标志;等待事件标志时RTOS会运行别的任务。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|Archiver|手机版|硬汉嵌入式论坛

GMT+8, 2024-5-24 17:57 , Processed in 0.270368 second(s), 28 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表