K60的SPI2与DMA数据传输

K60SPI通信中,相位时序必须相同,在多字节传输方式下,主机传输为:发送一个字节,无论从机是否接受到数据,主机都会读取一个数据,如果没有返回值则显示0.因此从机接受必须要使用中断来改善接收时序。否则会乱。SPI中,主机发送一个数据后,会在一定时间内自动接收数据。如果没有数据则接受0.如果在主机没有发数据给从机的情况下,从机发送数据给主机,主机不会接受。即使设置了中断标志位,也不会产生中断。JLINK,刷固件有时候回莫名其妙显示灯不亮,此时是因为ATM的固件损坏,需要刷固件。方法很简单。

传输网上例程:

void DMA_Init(void){//时钟配置

SIM_SCGC6 |= SIM_SCGC6_DMAMUX_MASK;//打开DMA通道多路复用器时钟

SIM_SCGC7 |= SIM_SCGC7_DMA_MASK;//打开DMA模块时钟//通道配置,配置寄存器DMAMUX_CHCFG_REG:使能、触发、源设置//通道有16个,源64个. 源21为SPI2发送。DMAMUX_CHCFG5 = 0x00;//clear.

DMAMUX_CHCFG5 = DMAMUX_CHCFG_ENBL_MASK | DMAMUX_CHCFG_TRIG_MASK | DMAMUX_CHCFG_SOURCE(21);//其他寄存器配置

DMA_CR = DMA_CR_EMLM_MASK | DMA_CR_CLM_MASK | DMA_CR_EDBG_MASK;//32位,CX(取消传输),EMLM(Enable minor loop mapping)//

DMA_ERQ = DMA_ERQ_ERQ5_MASK;//使能请求。enableDMA request n(0,1,2,。。。,15)。DMA相应通道的请求信号使能DMA_CDNE = 0x05;//清除传输完成bitDMA_CINT = 0x05;//清除中断//TCD配置

DMA_TCD5_SADDR = (uint32)memForDMA1; //4 bytes.DMA_TCD5_SOFF = 4; //源地址每次4个字节的偏移

DMA_TCD5_ATTR = DMA_ATTR_SSIZE(2) | DMA_ATTR_DSIZE(2); //传输宽度8 bits, 16 bits,32 bits,对应着0,1,2

DMA_TCD5_DADDR = (uint32)SPI2_BASE_PTR + 0x00000034; //PUSHR寄存器地址0x400AC034u。

DMA_TCD5_DOFF = 0;//目的地址要一直保持不变(PUSHR的地址)。

DMA_TCD5_DLASTSGA = 0; //最后一个地址后的目的地址处理。

DMA_TCD5_CSR = DMA_CSR_INTMAJOR_MASK; //major loop 完成后,产生一个中断。[ACTIVE][START][DONE][INT_MAJ][INT_HALF]//添加DMA_CSR_BWC(2)后,每屏传到SPI上的数据增加了一些。//添加DMA_CSR_INTHALF_MASK后似乎无影响。用于PING-PING buffer。//DREQ bit is SET,then 主迭代完成时将ERQ位清零。DMA_TCD5_CSR |= DMA_CSR_BWC(1) | DMA_CSR_DREQ_MASK;//Minor loop mapping is enabled(CR[EMLM]=1) & Minor loop offset enabled(SMLOE or DMLOE=1)

DMA_TCD5_NBYTES_MLOFFYES = 1024;;//

DMA_TCD5_CITER_ELINKNO = 4; //current major interation count.当前主迭代剩余次数。DMA_TCD5_BITER_ELINKNO = 4; //starting major interation count.与上一个变量的数值时钟相等。主迭代次数。

DMA_TCD5_SLAST = -4096; //到最后一个minor loop后的源地址处理处理}

===============================================================================================================================================================================================================

=============================================

自己例程:

void dma_portx2buff_init(DMA_CHn CHn, void *SADDR, void *DADDR, uint32 count, uint32 cfg)

SIM_SCGC7 |= SIM_SCGC7_DMA_MASK; //打开DMA模块时钟 SIM_SCGC6 |= SIM_SCGC6_DMAMUX_MASK; //打开DMA多路复用器时钟

DMA_SADDR(CHn) = (uint32)SADDR; // 设置 源地址 //DMA_SADDR(CHn) = (uint32)SPI2_BASE_PTR + 0x00000038; //这两种源地址给定方式都行,但不能将FIFO缓冲寄存器的地址给它。

DMA_DADDR(CHn) = (uint32)DADDR; // 设置目的地址 DMA_SOFF(CHn) = 0; // 设置源地址偏移 = 0x0, 即不变 DMA_DOFF(CHn) = 4; // 每次传输后,目的地址加 BYTEs DMA_ATTR(CHn) = (0 | DMA_ATTR_SMOD(0x0) // 源地址模数禁止 Source address modulo feature is disabled | DMA_ATTR_SSIZE(2) // 源数据位宽 :DMA_BYTEn 。 SSIZE = 0 -> 8-bit ,SSIZE = 1 -> 16-bit ,SSIZE = 2 -> 32-bit ,SSIZE = 4 -> 16-byte | DMA_ATTR_DMOD(0x0) // 目标地址模数禁止 | DMA_ATTR_DSIZE(2) // 目标数据位宽 :DMA_BYTEn 。 设置参考 SSIZE );

DMA_CITER_ELINKNO(CHn) = DMA_CITER_ELINKNO_CITER(count); //当前副循环次数 ,这里count=2

DMA_BITER_ELINKNO(CHn) = DMA_BITER_ELINKNO_BITER(count); //起始副循环次数

DMA_CR &= ~DMA_CR_EMLM_MASK; // CR[EMLM] = 0 关闭副循环映射 //当CR[EMLM] = 0 时:

DMA_NBYTES_MLNO(CHn) =8; // 通道每次传输字节数,这里设置为BYTEs个字节。注:值为0表示传输4GB */ // DMA_NBYTES_MLNO(CHn) ;

DMA_SLAST(CHn) = 0; //调整 源地址的附加值,主循环结束后恢复 源地址 //

DMA_DLAST_SGA(CHn) = (uint32)(-16)

; //调整目的地址的附加值,主循环结束后恢复目的地址或者保持地址 DMA_DLAST_SGA(CHn) =0; DMA_CSR(CHn) = (0 | DMA_CSR_BWC(3) //带宽控制,每读一次,eDMA 引擎停止 8 个周期(0不停止;1保留;2停止4周期;3停止8周期) // | DMA_CSR_DREQ_MASK //主循环结束后停止硬件请求 | DMA_CSR_INTMAJOR_MASK //主循环结束后产生中断 ); DMAMUX_CHCFG_REG(DMAMUX_BASE_PTR, CHn) = (0 | DMAMUX_CHCFG_ENBL_MASK //| DMAMUX_CHCFG_TRIG_MASK | DMAMUX_CHCFG_SOURCE(20) ); DMA_DIS(CHn);

//使能通道CHn 硬件请求 DMA_IRQ_CLEAN(CHn); //DMA_EN(CHn); //使能通道CHn 硬件请求 //DMA_IRQ_EN(CHn); //允许DMA通道传输}void


DMA_DIS(DMA_CH0);

DMA_IRQ_CLEAN(DMA_CH0); //清除通道传输中断标志位 DMA_IRQ_EN(DMA_CH0);

DMA_EN(DMA_CH0);

void DMA0_IRQHandler()

{ if(SPI_SR_REG(SPI2_BASE_PTR)&SPI_SR_RFDF_MASK) //接收非空,值为1 // { // SPIRx_isrheader(SPI2,SPI_PCS0,piex); uart_putchar(UART1,rec[0]); uart_putchar(UART1,rec[1]); uart_putchar(UART1,rec[2]); DELAY_US(100); uart_putchar(UART1,rec[3]);

h++;

}

SPI_SR_REG(SPI2_BASE_PTR)|=SPI_SR_RFDF_MASK; //清除标志位 DMA_IRQ_CLEAN(DMA_CH0); //清除通道传输中断标志位}}

============================================================================================================================================================================================================================================================

DMA模块理解初始化步骤:初始化时钟--寄存器初始化--开相应中断--中断函数初始化中,16个通道任意选择一个通道,选择一个触发源,即可以对应,并没有哪个通道负责哪几个特定的触发源。(K60DMA中含有SPI2的触发,手册上可能没有)

========================DMA运行思想:有主循环和副循环两个循环。需要设置好相应源地址,目的地址,源/目的地址的偏移地址。从spi通过dma到数组,spi为32位,1个地址对应4个字节,数组为8位,一个地址对应一个字节。偏移地址量DMA_SOFF(CHn)表示的是一个地址的字节数量。并设置好主循环次数。这里偏移量根据定义的数组或则寄存器的位数来确定,比如寄存器为32位,则定义源地址偏移量为4个字节(不偏移则为0)。赋给SOFF的值为4(0)。 寄存器DMA_CITER_ELINKNO值为副循环次数,与DMA_BITER_ELINKNO两则定义相同。

DMA_SADDR(CHn) = (uint32)SPI2_BASE_PTR + 0x00000038; DMA_DADDR(CHn) = (uint32)DADDR; // 设置目的地址 DMA_SOFF(CHn) = 0; // 设置源地址偏移 = 0x0, 即不变 DMA_DOFF(CHn) = 0; // 每次传输后,目的地址加 BYTEs DMA_CITER_ELINKNO(CHn) = DMA_CITER_ELINKNO_CITER(count); //当前副循环次数

DMA_BITER_ELINKNO(CHn) = DMA_BITER_ELINKNO_BITER(count); //起始副循环次数

========================设置好传输完一个副循环或则主循环后源地址和目的地址的偏移量。传输一个字节则设置为不偏移为0。偏移地址设置:副循环次数乘以副循环每次传输的字节数,eg, DMA_NBYTES_MLNO(CHn)=8;表示每次副循环传输8个字节,DMA_CITER_ELINKNO=2。

副循环两次,那么需要设置目标偏移地址DMA_DLAST_SGA(CHn)=(uint32)(-16);偏移设置含义为每个大主循环(而非副循环)之后的地址增减量,因此需要相乘。 DMA_SLAST(CHn) = 0; //调整 源地址的附加值,主循环结束后恢复 源地址 //

DMA_DLAST_SGA(CHn) = (uint32)((cfg & DADDR_KEEPON ) == 0 ? (-count) : 0 ); //调整目的地址的附加值,主循环结束后恢复目的地址或者保持地址

DMA_DLAST_SGA(CHn) =(uint32)(0);

========================设置好传输数据的位数,一般位数相同。无论定义的数组是32位还是8位。 DMA_ATTR(CHn) = (0 | DMA_ATTR_SMOD(0x0) // 源地址模数禁止 Source address modulo feature is disabled | DMA_ATTR_SSIZE(2) // 源数据位宽 :DMA_BYTEn 。 SSIZE = 0 -> 8-bit ,SSIZE = 1 -> 16-bit ,SSIZE = 2 -> 32-bit ,SSIZE = 4 -> 16-byte | DMA_ATTR_DMOD(0x0) // 目标地址模数禁止 | DMA_ATTR_DSIZE(2) // 目标数据位宽 :DMA_BYTEn 。 设置参考 SSIZE );

========================对CR寄存器EMLM位清除后,必须要申明每个DMA_NBYTES_MLNO中的参数,表示每次副循环传输的字节数。

DMA_CR &= ~DMA_CR_EMLM_MASK; // CR[EMLM] = 0 //当CR[EMLM] = 0 时:

DMA_NBYTES_MLNO(CHn) = DMA_NBYTES_MLNO_NBYTES(4); // 通道每次传输字节数,这里设置为BYTEs个字节。*/

========================选择触发源与打开中断,如果要一直中断则清除设置 | DMA_CSR_DREQ_MASK //主循环结束后停止硬件请求。如果只需要一次传输,则设置 | DMA_CSR_DREQ_MASK //主循环结束后停止硬件请求。 DMA_DLAST_SGA(CHn) =0; DMA_CSR(CHn) = (0 | DMA_CSR_BWC(3) //带宽控制,每读一次,eDMA 引擎停止 8 个周期(0不停止;1保留;2停止4周期;3停止8周期) // | DMA_CSR_DREQ_MASK //主循环结束后停止硬件请求 | DMA_CSR_INTMAJOR_MASK //主循环结束后产生中断 );

DMAMUX_CHCFG_REG(DMAMUX_BASE_PTR, CHn) = (0 | DMAMUX_CHCFG_ENBL_MASK //| DMAMUX_CHCFG_TRIG_MASK | DMAMUX_CHCFG_SOURCE(20) );



对于主循环的理解:你需要传输多少次数据,传输的次数值存储在主循环计器DMA_CITER_ELINKNO(CHn)的CITER中,每次传输完数据之后,CITER的值就会自动减一,一旦主循环计数器的CITER的值减到0,则DMA传输完毕后,TCDn_C

SR[DONE]置1,如果允许,可以向CPU申请中断,读走这部分数据(比如我们要对一个传感器的电压值,采集100次,则应该将主循环的次数就设定为100,也就是DMA_CITER_ELINKNO(CHn)的CITER的数值设定为100)。

对于目的地址的理解:当DMA存储数据的时候,也就是每次数据传输之后,目的地址会自动加上DMA_DOFF(CHn)这个目的地址偏移量的值。如果偏移量为0时,每次传输数据之后目的地址不会发生变化,这样一来,如果有多次数据的传输(也就是主循环的次数),则就会造成对上次数据的更新覆盖( DMA_count_init()就是这样,只不过其实它并没有利用存储在COUNTSADDR这个地址的数据值,而是利用DMA_CITER_ELINKNO(CHn)的CITER值在每次数据传输传输完成之后,值会自动减一,从而达到累加计数的目的)。如果DMA_DOFF(CHn)等于BYTEn(每次DMA传输字节数),这样以来,如果有多次数据的传输,那数据在内存里面的地址就会变成连续的储存,而不会造成对上次数据的覆盖。DMA_SLAST(CHn)和DMA_DLAST_SGA(CHn)分别为当DMA传输结束后(主循环结束后),对于源地址和目的地址的最终偏移调整值。如果其值都为0,则下次需要DMA传输数据的时候,则此次数据组将会覆盖上次的数据组。(对于AD采样的话,应该可以理解为,如果我先对1号传感器采集100个数据,数据依次经过DMA存储在内存中,形成这次的数据组。当我们读走这个数据组之后,我们又需要对2号传感器采集100个数据,这100个数据就会覆盖上次的数据,从而形成此次的数据组。这样以来我们应该可以定义一个 ADC_result[100] 用来存储每次传感器的100数据)所以对于目的地址,我的理解就是,目的地址就是每次DMA收到数据后,这个数据在内存中的存储位置。

而对于源地址,简单理解可以通过内存中数据的复制这类问题来理解。比如通过设定一些配置参数后,DMA按照设定好的顺序从0x1000(源地址)取出数据,在将此数据复制到0X2000(目的地址)中,这样就实现了内存中数据的复制,并且不需要CPU的干预,也就是所DMA就是将源地址的数据经过设定好的规律直接搬移到目的地址(但是此时数据总线是被DMA在利用,所以数据进行复制的时候,CPU并不能对内存进行访问,所以才有什么数据总线的分时复用吧,纯属于个人理解)。下面我们把它简单应用在ADC采样的问题上,因为一个AD采样,其采样的结果保存在数据结果寄存器(ADCx_Rn)中,并且此结果寄存器在内存中肯定是映射一个地址。因此,我们就可以把DMA的源地址设定为这个地址,这样一来,就可以把AD的采样数据就可以通过DMA直接保存在内存中。并且如果每次读取源地址的数据

之后,源地址的偏移值为0,换句话说就是源地址一直保持不变,依旧指向AD采样的结果值,这样下次AD的采样值就也可以不断的保存在相应的目的地址中了。而对应的,我们只要把目的地址偏移量设定为1,也就说每次储存数据之后,目的地址会加一,这样一来,AD的采样值在内存中就会以地址连续的方式存储了。K60是32位单片机,所以其内存中的一个地址就可以保存4个字节(32位)的数据,由于AD的数据精度可以选择为8bit、10bit、12bit、16bit,但是对于一个结果整体而言,其任然是以32位的形式存储,所以源数据宽度也就应该设定为4个字节,目的数据宽度也应该与之匹配,也设定为4个字节,否则数据的传输就会出现错误了。假如源数据宽度为1个字节,源地址为0X1000,那么源地址里面的数据就会分为4个字节,分别进过四次读取之后,再交给目的地址。这样一来,一个完整的数据就会被拆为4个数据,这样数据传输就出错了。

对于触发源的理解:Trigger(触发源)和 Peripheral Request 一起构成了DMA Request (数据采集的控制信号)。DMA有三种工作模式:无效模式、正常模式、和周期触发模式。周期触发模式:触发源是PIT(周期定时器中断),这种模式仅仅适用与DMA Channel 0 -- DMA Channel 3。正常工作模式:触发源是由DMAMUX_CHCFG_REG的SOURCE[0:5]决定的,它们之间的映射关系野火的ADC驱动中DMA_sources枚举已经定义好了。触发源包含很多,有UART、SPI、ADC、FTM、PDB、CPM、I2C、IO口触发,以及 Always On(一直使能,DMA Request完全由Peripheral Request决定,但是这个Peripheral Request,我不知道它是怎么来的,从这里我就开始理解不清楚了) 。其中看到例程中用的最多是IO口触发了,这个比较好理解。但是触发源还可以ADCn等触发,这个我也不理解了。

相关文档
最新文档