DMA实现数据传输流程

您所在的位置:网站首页 dma搬运 DMA实现数据传输流程

DMA实现数据传输流程

#DMA实现数据传输流程| 来源: 网络整理| 查看: 265

1.相关概念 (1)DMA直接寄存器访问

可实现外设数据寄存器到存储器、存储器到外设数据寄存器、存储器到存储器之间的高效数据传输,无需CPU操作控制。

(2)外设与存储器

外设包括ADC、SPI、I2C、USART等等 存储器包括片内SRAM、外部存储器、片内Flash等等

(3)具体应用场合举例:

ADC采集可以利用DMA将AD转换数据转移到目标存储区,适用于多通道采集、采样频率高、连续传输的ADC采集场合; 将特定存储区的数据转移到外设数据寄存器,用于外设的对外数据传输,如存储器传输数据到串口数据寄存器,串口发送数据到PC端; 存储器到存储器的数据传输利用DMA可以达到更高的传输效率,不占用CPU,从而节约CPU资源。

2.DMA功能框图

图1 在这里插入图片描述

(1)①外设通道

STM32F4系列有两个DMA控制器,每个控制器有8个数据流,每个数据流又对应8个数据通道(8个通道对应的是8个外设请求)。 DMA控制器通过DMA数据流x配置寄存器DMA_SxCR(x为0~7,对应8个DMA数据流)的CHSEL[2:0]位选择对应的通道即选择对应的外设。 DMA请求映射表如下: 在这里插入图片描述 在这里插入图片描述 每个外设请求都占用一个数据流通道,相同外设请求可以占用不同数据流通道。比如SPI3_RX请求,占用DMA1的数据流0的通道0,因此使用该请求时,需要在把DMA_S0CR寄存器的CHSEL[2:0]设置为“000”, 此时相同数据流的其他通道不被选择,处于不可用状态,比如此时不能使用数据流0的通道1即I2C1_RX请求。

(2)②仲裁器

由仲裁器判断哪个数据流优先传输 DMA_SxCR寄存器PL[1:0]位,可以设置为非常高、高、中和低四个级别; 当两个及以上数据流软件设置优先级相同时,比较数据流编号,编号小的优先级高。

(3)③FIFO

每个数据流都独立拥有四级32位FIFO(先入先出存储器缓存区,队列)。DMA传输分为FIFO模式和直接模式。 直接模式:收到外设请求会立即启动传输。 FIFO模式:在数据传输到目标地址前临时存放这些数据,通过DMA数据流xFIFO控制寄存器DMA_SxFCR的FTH[1:0]位来控制FIFO的阈值,分别为1/4、1/2、3/4和满。数据缓冲量达到阈值级别,将FIFO中的数据传输到目标地址中。 FIFO主要用于源地址和目标地址要求数据宽度不同的场合,比如源数据是源源不断的字节数据,而目标地址要求输出字宽度的数据,即在实现数据传输时同时把原来4个8位字节的数据拼凑成一个32位字数据。此时使用FIFO功能先把数据缓存起来,根据需要输出数据。 FIFO还用于突发(burst)传输。

(4)④存储器端口、⑤外设端口

DMA2(DMA控制器2)的存储器端口和外设端口都是连接到AHB总线矩阵,可以使用AHB总线矩阵功能。DMA2存储器和外设端口可以访问相关的内存地址,包括有内部Flash、内部SRAM、AHB1外设、AHB2外设、APB1、APB2外设和外部存储器空间。 DMA1的存储区端口相比DMA2的要减少AHB2外设的访问权,同时DMA1外设端口是没有连接至总线矩阵的,只有连接到APB1外设,所以DMA1不能实现存储器到存储器传输。

3.DMA传输模式

DMA2支持全部三种传输模式;DMA1只支持外设到存储器、存储器到外设两种模式。 (1)模式选择:DMA_SxCR寄存器的DIR[1:0]位,“00”外设到存储器;“10”存储器到存储器;“01”存储器到外设。 (2)传输使能:DMA_SxCR寄存器的EN位置1 (3)数据宽度:DMA_SxCR寄存器的PSIZE[1:0]和MSIZE[1:0]位分别指定外设和存储器数据宽度大小,可以指定为字节(8位)、半字(16位)和字(32位)。直接模式要求外设和寄存器数据宽度一致,该模式下DMA数据流只使用PSIZE,MSIZE被忽略。 (4)地址设置: ①外设地址:DMA_SxPAR寄存器(x为0~7) ②存储器地址:DMA_SxM0AR、DMA_SxM1AR,其中DMA_SxM1AR只用于双缓冲模式。 (5)循环模式与一次模式:一次模式时传输一次就停止传输,下一次传输需要手动控制;循环模式是传输一次后自动按照相同配置重新传输,直至被控制停止或传输错误。循环模式使能是DMA_SxCR寄存器的CIRC位。 (6)单次传输与突发传输:突发传输就是在传输阶段把速度瞬间提高,实现高速传输,在数据传输完成后恢复正常速度,有点类似达到数据块“秒传”效果。为达到这个效果突发传输过程要占用AHB总线,保证要求每个数据项在传输过程不被分割,这样一次性把数据全部传输完才释放AHB总线;而单次传输时必须通过AHB的总线仲裁多次控制才传输完成。 在这里插入图片描述 其中PBURST[1:0]和MBURST[1:0]位是位于DMA_SxCR寄存器中的, 用于分别设置外设和存储器不同节拍数的突发传输,对应为单次传输、4个节拍增量传输、8个节拍增量传输和16个节拍增量传输。 PINC位和MINC位是寄存器DMA_SxCR寄存器的第9和第10位,如果位被置1则在每次数据传输后数据地址指针自动递增, 其增量由PSIZE和MSIZE值决定,比如,设置PSIZE为半字大小,那么下一次传输地址将是前一次地址递增2。 在这里插入图片描述 突发传输与FIFO密切相关,突发传输需要结合FIFO使用,具体要求FIFO阈值一定要是内存突发传输数据量的整数倍。 FIFO阈值选择和存储器突发大小必须配合使用。 (7)直接模式与双缓冲模式 默认情况下,DMA工作在直接模式,不使能FIFO阈值级别。 ①直接模式:每个外设请求都立即启动对存储器传输的单次传输。直接模式要求源地址和目标地址的数据宽度必须一致,所以只有PSIZE控制,而MSIZE值被忽略。突发传输是基于FIFO的所以直接模式不被支持。另外直接模式不能用于存储器到存储器传输。 ②双缓冲模式:DMA_SxCR寄存器的DBM位置1可启动双缓冲传输模式,并自动激活循环模式。双缓冲不应用于存储器到存储器的传输。双缓冲模式下,两个存储器地址指针都有效,即DMA_SxM1AR寄存器将被激活使用。开始传输使用DMA_SxM0AR寄存器的地址指针所对应的存储区,当这个存储区数据传输完DMA控制器会自动切换至DMA_SxM1AR寄存器的地址指针所对应的另一块存储区,如果这一块也传输完成就再切换至DMA_SxM0AR寄存器的地址指针所对应的存储区,这样循环调用。 当其中一个存储区传输完成时都会把传输完成中断标志TCIF位置1,如果我们使能了DMA_SxCR寄存器的传输完成中断,则可以产生中断信号。 DMA_SxCR寄存器的CT位,当DMA控制器是在访问使用DMA_SxM0AR时CT=0,此时CPU不能访问DMA_SxM0AR,但可以向DMA_SxM1AR填充或者读取数据;当DMA控制器是在访问使用DMA_SxM1AR时CT=1,此时CPU不能访问DMA_SxM1AR,但可以向DMA_SxM0AR填充或者读取数据。另外在未使能DMA数据流传输时,可以直接写CT位,改变开始传输的目标存储区。 在这里插入图片描述 (8)流控制器 在传输之前设置DMA_SxNDTR寄存器为要传输数目值,DMA控制器在传输完这么多数目数据后就可以控制DMA停止传输。 DMA数据流x数据项数DMA_SxNDTR(x为0~7)寄存器用来记录当前仍需要传输数目,是一个16位数据有效寄存器,最大值为65535。编程时一般都会明确指定一个传输数量, 在完成一次数据传输后DMA_SxNDTR计数值就会自减,当达到零时就说明传输完成。 如果某些情况下在传输之前我们无法确定数据的数目,那DMA就无法自动控制传输停止了, 此时需要外设通过硬件通信向DMA控制器发送停止传输信号。只有SDIO可以发出停止传输信号,其他外设不具备此功能。

4.DMA中断

(1)达到半传输:DMA数据传输达到一半时HTIF标志位被置1,如果使能HTIE中断控制位将产生达到半传输中断; (2)传输完成:DMA数据传输完成时TCIF标志位被置1,如果使能TCIE中断控制位将产生传输完成中断; (3)传输错误:DMA访问总线发生错误或者在双缓冲模式下试图访问“受限”存储器地址寄存器时TEIF标志位被置1,如果使能TEIE中断控制位将产生传输错误中断; (4)FIFO错误:发生FIFO下溢或者上溢时FEIF标志位被置1,如果使能FEIE中断控制位将产生FIFO错误中断; (5)直接模式错误:在外设到存储器的直接模式下,因为存储器总线没得到授权,使得先前数据没有完成被传输到存储器空间上,此时DMEIF标志位被置1,如果使能DMEIE中断控制位将产生直接模式错误中断。

5.DMA初始化结构体 (1)DMA_InitTypeDef初始化结构体 typedef struct { uint32_t Channel; //通道选择 uint32_t Direction; //传输方向 uint32_t PeriphInc; //外设递增 uint32_t MemInc; //存储器递增 uint32_t PeriphDataAlignment; //外设数据宽度 uint32_t MemDataAlignment; //存储器数据宽度 uint32_t Mode; //模式选择 uint32_t Priority; //优先级 uint32_t FIFOMode; //FIFO模式 uint32_t FIFOThreshold; //FIFO阈值 uint32_t MemBurst; //存储器突发传输 uint32_t PeriphBurst; //外设突发传输 } DMA_InitTypeDef;

①PeriphInc:如果配置为PeriphInc_Enable,即使能外设地址自动递增(设定DMA_SxCR寄存器的PINC位的值)。一般外设只有一个数据寄存器,因此一般不会使能该位。ADC3的数据寄存器地址是固定并且只有一个所以不使能外设地址递增 ②MemInc:如果配置为MemInc_Enable,使能存储器地址自动递增功能(设定DMA_SxCR寄存器的MINC位的值);我们自定义的存储区一般都是存放多个数据的,所以使能存储器地址自动递增功能。之前定义了一个包含4个元素的数组用来存放数据,使能存储区地址递增功能,自动把每个通道数据存放到对应数组元素内。 ③PeriphDataAlignment:外设数据宽度,可选字节(8位)、半字(16位)和字(32位),它设定DMA_SxCR寄存器的PSIZE[1:0]位的值。ADC数据寄存器只有低16位数据有效,使用半字数据宽度。 ④MemDataAlignment:存储器数据宽度,可选字节(8位)、半字(16位)和字(32位),它设定DMA_SxCR寄存器的MSIZE[1:0]位的值。保存ADC转换数据也要使用半字数据宽度,这跟我们定义的数组是相对应的。 ⑤Mode:DMA传输模式选择,可选一次传输或者循环传输,它设定DMA_SxCR寄存器的CIRC位的值。我们希望ADC采集是持续循环进行的,所以使用循环传输模式。 ⑥FIFOMode:FIFO模式使能,如果设置为DMA_FIFOMode_Enable表示使能FIFO模式功能(设定DMA_SxFCR寄存器的DMDIS位)。ADC采集传输使用直接传输模式即可,不需要使用FIFO模式。 ⑦MemBurst:存储器突发模式选择,可选单次模式、4节拍的增量突发模式、8节拍的增量突发模式或16节拍的增量突发模式,它设定DMA_SxCR寄存器的MBURST[1:0]位的值。ADC采集传输是直接模式,要求使用单次模式。 ⑧PeriphBurst:外设突发模式选择,可选单次模式、4节拍的增量突发模式、8节拍的增量突发模式或16节拍的增量突发模式,它设定DMA_SxCR寄存器的PBURST[1:0]位的值。ADC采集传输是直接模式,要求使用单次模式。

(2)DMA_HandleTypeDef初始化结构体 typedef struct __DMA_HandleTypeDef { DMA_Stream_TypeDef *Instance; //注册基地址 DMA_InitTypeDef Init; //DMA通信参数 HAL_LockTypeDef Lock; //DMA锁定对象 __IO HAL_DMA_StateTypeDef State; //DMA传输状态 void *Parent; //父类指针 void (* XferCpltCallback)( struct __DMA_HandleTypeDef * hdma); //DMA传输完成回调函数 void (* XferHalfCpltCallback)( struct __DMA_HandleTypeDef * hdma); //DMA传输完成一半回调函数 void (* XferM1CpltCallback)( struct __DMA_HandleTypeDef * hdma); //Memory1 DMA传输完成回调函数 void (* XferM1HalfCpltCallback)( struct __DMA_HandleTypeDef * hdma); //Memory1 DMA传输完成一半回调函数 void (* XferErrorCallback)( struct __DMA_HandleTypeDef * hdma); //DMA传输错误回调函数 void (* XferAbortCallback)( struct __DMA_HandleTypeDef * hdma); //DMA传输中止回调函数 __IO uint32_t ErrorCode; //DMA错误码 uint32_t StreamBaseAddress; //DMA数据流基地址 uint32_t StreamIndex; //DMA数据流索引 } DMA_HandleTypeDef;

①Instance: 指向DMA数据流基地址的指针,即指定使用哪个DMA数据流。可选数据流0至数据流7。 例如,我们使用模拟数字转换器ADC3规则采集4个输入通道的电压数据, 查表可知可以使用数据流0或者数据流1,这里支持两个数据流是为了避免多个通道使用时发生冲突,提供备选数据流可选。 ②Init:这里包含上面介绍DMA_InitTypeDef结构体的所有参数的初始化。 ③Lock:DMA锁定对象。DMA进程锁,通常都在DMA传输设置开始前锁上进程锁,设置完毕后释放进程锁。 ④State:DMA传输状态。它包含六种状态, 复位状态,尚未初始化或者失能; 就绪状态,已经完成初始化,随时可以传输数据; 传输忙,DMA传输进程正在进行; 传输超时状态; 传输错误状态; 传输中止状态。 ⑤*Parent:父类指针。只要将该指针指向一些ADC、UART等外设的handle类,就等于完成了继承。 ⑥传输过程中的回调函数:包括传输完成,传输完成一半,传输错误,传输中止回调函数。这些回调函数中可以加入用户的处理代码。 ⑦ErrorCode:DMA错误码,包含 无错误:HAL_DMA_ERROR_NONE, 传输错误:HAL_DMA_ERROR_TE, FIFO错误:HAL_DMA_ERROR_FE, 直接模式错误:HAL_DMA_ERROR_DME, 超时错误:HAL_DMA_ERROR_TIMEOUT, 参数错误:HAL_DMA_ERROR_PARAM, 没有回调函数正在执行退出请求错误:HAL_DMA_ERROR_NO_XFER, 不支持模式错误:HAL_DMA_ERROR_NOT_SUPPORTED。 ⑧StreamBaseAddress:DMA数据流基地址,用来根据定义句柄计算数据流的基地址。 ⑨StreamIndex:DMA数据流索引,根据数据流的序号来确定数据流的偏移地址。

6.DMA_MemToMem 存储器到存储器传输

代码实现步骤: ①使能DMA数据流时钟并复位初始化DMA数据流; ②配置DMA数据参数; ③使能DMA数据流,进行传输; ④等待传输完成,并对源数据和目标地址数据进行比较; 存储器到存储器传输必须使用DMA2,但对数据流编号以及通道选择就没有硬性要求,可以自由选择。只能使用一次传输模式不能循环传输。

typedef enum { FAILED = 0, PASSED = !FAILED } TestStatus; DMA_HandleTypeDef hdma_memtomem_dma2_stream0; static const uint32_t SRC_Const_Buffer[32]= { 0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10, 0x11121314,0x15161718,0x191A1B1C,0x1D1E1F20, 0x21222324,0x25262728,0x292A2B2C,0x2D2E2F30, 0x31323334,0x35363738,0x393A3B3C,0x3D3E3F40, 0x41424344,0x45464748,0x494A4B4C,0x4D4E4F50, 0x51525354,0x55565758,0x595A5B5C,0x5D5E5F60, 0x61626364,0x65666768,0x696A6B6C,0x6D6E6F70, 0x71727374,0x75767778,0x797A7B7C,0x7D7E7F80}; uint32_t DST_Buffer[32]; __IO TestStatus TransferStatus= FAILED; int main(void) { HAL_StatusTypeDef har_status; /* 复位所有外设,初始化Flash接口和系统滴答定时器 */ HAL_Init(); /* 配置系统时钟 */ SystemClock_Config(); /* 板载LED初始化 */ LED_GPIO_Init(); /* DMA初始化 */ MX_DMA_Init(); //开始DMA传输 har_status=HAL_DMA_Start(&hdma_memtomem_dma2_stream0,(uint32_t)&SRC_Const_Buffer,(uint32_t)&DST_Buffer,32); if(har_status==HAL_OK) { /* 检查发送和接收的数据是否相等 */ TransferStatus = Buffercmp(SRC_Const_Buffer, DST_Buffer, 32); /* 如果接收和发送的数据都是相同的,则通过 */ if(TransferStatus == 1) { LED1_ON; } /* 如果接收和发送的数据不同,则传输出错 */ else { LED2_ON; } } else { LED3_ON; } /* 无限循环 */ while (1) { } } /** * 函数功能: 判断指定长度的两个数据源是否完全相等 * 输入参数: 无 * 返 回 值: 无 * 说 明: 如果完全相等返回1,只要其中一对数据不相等返回0 */ uint8_t Buffercmp(const uint32_t* pBuffer, uint32_t* pBuffer1, uint16_t BufferLength) { /* 数据长度递减 */ while (BufferLength--) { /* 判断两个数据源是否对应相等 */ if (*pBuffer != *pBuffer1) { /* 对应数据源不相等马上退出函数,并返回0 */ return 0; } /* 递增两个数据源的地址指针 */ pBuffer++; pBuffer1++; } /* 完成判断并且对应数据相对 */ return 1; } /** * 函数功能: DMA配置 * 输入参数: 无 * 返 回 值: 无 * 说 明: 无 */ void MX_DMA_Init(void) { /* 使能DMA控制器时钟 */ __HAL_RCC_DMA2_CLK_ENABLE(); /* 配置DMA通道工作方式 */ hdma_memtomem_dma2_stream0.Instance = DMA2_Stream0;//数据流0 hdma_memtomem_dma2_stream0.Init.Channel = DMA_CHANNEL_0;//通道0 hdma_memtomem_dma2_stream0.Init.Direction = DMA_MEMORY_TO_MEMORY;//数据传输方向:存储器->存储器 hdma_memtomem_dma2_stream0.Init.PeriphInc = DMA_PINC_ENABLE;//外设地址不变 hdma_memtomem_dma2_stream0.Init.MemInc = DMA_MINC_ENABLE;//内存地址自增 hdma_memtomem_dma2_stream0.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;//传输数据半字16位 hdma_memtomem_dma2_stream0.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;//接收数据半字16位 hdma_memtomem_dma2_stream0.Init.Mode = DMA_NORMAL;//发一次开启一次 hdma_memtomem_dma2_stream0.Init.Priority = DMA_PRIORITY_HIGH;//优先级(设置为最高优先级) hdma_memtomem_dma2_stream0.Init.FIFOMode = DMA_FIFOMODE_DISABLE; hdma_memtomem_dma2_stream0.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL; hdma_memtomem_dma2_stream0.Init.MemBurst = DMA_MBURST_SINGLE; hdma_memtomem_dma2_stream0.Init.PeriphBurst = DMA_PBURST_SINGLE; HAL_DMA_Init(&hdma_memtomem_dma2_stream0); } 7.DMA串口收发数据 //main.c #define SEND_BUF_SIZE (8800) //发送数据长度,最好等于sizeof(TEXT_TO_SEND)+2的整数倍. u8 SendBuff[SEND_BUF_SIZE]; //发送数据缓冲区 const u8 TEXT_TO_SEND[] = {"ALIENTEK Pandora STM32L4 IOT DMA 串口实验"};//显示出来是42 汉字1Byte,空格2Byte,字母数字1Byte 再加上\n\r 共44个Bytes //使用了const关键字修饰,即常量类型,使得变量存储在内部flash空间上。 int main(void) { u8 t = 0, j, mask; u16 i; float pro = 0; //进度 HAL_Init(); SystemClock_Config(); //初始化系统时钟为80M delay_init(80); //初始化延时函数 80M系统时钟 uart_init(115200); //初始化串口,波特率为115200 LED_Init(); //初始化LED KEY_Init(); //初始化按键 LCD_Init(); //初始化LCD MYDMA_Config(DMA1_Channel4, DMA_REQUEST_2); //初始化DMA j = sizeof(TEXT_TO_SEND); for(i = 0; i if(mask) { SendBuff[i] = 0x0a; t = 0; } else { SendBuff[i] = 0x0d; mask++; } } else //复制TEXT_TO_SEND语句 { mask = 0; SendBuff[i] = TEXT_TO_SEND[t]; t++; } } while(1) { t = KEY_Scan(0); if(t == KEY0_PRES) //KEY0按下 { HAL_UART_Transmit_DMA(&UART1_Handler, (u8*)SendBuff, SEND_BUF_SIZE); //启动传输 //使能串口1的DMA发送 //等待DMA传输完成,此时我们来做另外一些事,点灯 //实际应用中,传输数据期间,可以执行另外的任务 while(1) { //DMA_HandleTypeDef UART1TxDMA_Handler; //DMA句柄 if(__HAL_DMA_GET_FLAG(&UART1TxDMA_Handler, DMA_FLAG_TC4)) //等待DMA2_Steam7传输完成 { __HAL_DMA_CLEAR_FLAG(&UART1TxDMA_Handler, DMA_FLAG_TC4); //清除DMA2_Steam7传输完成标志 HAL_UART_DMAStop(&UART1_Handler); //传输完成以后关闭串口DMA break; } pro = __HAL_DMA_GET_COUNTER(&UART1TxDMA_Handler); //返回当前DMA通道传输中剩余数据单元的数量 pro = 1 - pro / SEND_BUF_SIZE; //得到百分比 pro *= 100; //扩大100倍 } } delay_ms(10); } } //dma.c /** * @brief DMAx的各通道配置,这里的传输形式是固定的,这点要根据不同的情况来修改 * 从存储器->外设模式/8位数据宽度/存储器增量模式 * * @param DMA_Streamx DMA通道选择,DMA1_Channel0~7/DMA2_Channel0~7 * @param chx DMA请求选择,@ref DMA_REQUEST_0~DMA_REQUEST_7 * * @return void */ void MYDMA_Config(DMA_Channel_TypeDef *DMA_Channel, u32 seq) { if((u32)DMA_Channel > (u32)DMA2) //得到当前stream是属于DMA2还是DMA1 { __HAL_RCC_DMA2_CLK_ENABLE();//DMA2时钟使能 } else { __HAL_RCC_DMA1_CLK_ENABLE();//DMA1时钟使能 } __HAL_LINKDMA(&UART1_Handler, hdmatx, UART1TxDMA_Handler); //将DMA与USART1联系起来(发送DMA) //Tx DMA配置 UART1TxDMA_Handler.Instance = DMA_Channel; //通道选择 UART1TxDMA_Handler.Init.Request = seq; //请求选择 UART1TxDMA_Handler.Init.Direction = DMA_MEMORY_TO_PERIPH; //存储器到外设 UART1TxDMA_Handler.Init.PeriphInc = DMA_PINC_DISABLE; //外设非增量模式 UART1TxDMA_Handler.Init.MemInc = DMA_MINC_ENABLE; //存储器增量模式 UART1TxDMA_Handler.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; //外设数据长度:8位 UART1TxDMA_Handler.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; //存储器数据长度:8位 UART1TxDMA_Handler.Init.Mode = DMA_NORMAL; //外设普通模式 UART1TxDMA_Handler.Init.Priority = DMA_PRIORITY_HIGH; //中等优先级 HAL_DMA_DeInit(&UART1TxDMA_Handler); HAL_DMA_Init(&UART1TxDMA_Handler); }

未完待续。。。。。。。。。。。。。。。。。。。。。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3