依旧是开篇的逼逼叨叨。
自从上周换了新塘的M031,现在对这个MCU真的喜好,写代码巨舒适,大略快速就能上手。上周写了入门的UART的DMA,本日来搞一个SPI 的DMA通信,实用的外设,咱一个都不能少。

M031 SPI概述

SPI接口是全双工同步串行数据通讯接口,可做为主机或从机,用 4 线双向通讯。 M031 包含 1 组 SPI掌握器,用于对从外围设备吸收到的数据实行串并转换,并对发送到外围设备的数据进行并串转换。 每个SPI掌握器可以配置为主设备或从设备,并支持PDMA功能存取数据缓冲区。 每个SPI掌握器还支持I2S模式来连接外部音频编解码器。
特色:
– 1组 SPI 掌握器– 支持主机模式和从机模式– 传输位长可为 8 ~ 32位– 供应独立的4级32位(或8级16位)收发FIFO缓存,实际数据位长由SPI的设置决定– 支持高位优先(MSB)或低位优先(LSB)时序– 支持字节重排功能– 支持字节或字停息模式– 总线时钟主机模式最高可到24 MHz ,从机模式最高可到16 MHz (当芯片事情在 VDD =1.8~3.6V)– 支持一数据通道半双工传输– 支持只吸收模式– 支持 PDMA 传输
框图
代码片
时钟以及GPIO初始化
void SYS_Init(void){ /---------------------------------------------------------------------------------------------------------/ / Init System Clock / /---------------------------------------------------------------------------------------------------------/ / Unlock protected registers / SYS_UnlockReg(); / Enable HIRC clock (Internal RC 48MHz) / CLK_EnableXtalRC(CLK_PWRCTL_HIRCEN_Msk); / Wait for HIRC clock ready / CLK_WaitClockReady(CLK_STATUS_HIRCSTB_Msk); / Select HCLK clock source as HIRC and HCLK source divider as 1 / CLK_SetHCLK(CLK_CLKSEL0_HCLKSEL_HIRC, CLK_CLKDIV0_HCLK(1)); / Set both PCLK0 and PCLK1 as HCLK / CLK->PCLKDIV = CLK_PCLKDIV_APB0DIV_DIV1 | CLK_PCLKDIV_APB1DIV_DIV1; / Select IP clock source / / Select UART0 clock source is HIRC / CLK_SetModuleClock(UART0_MODULE, CLK_CLKSEL1_UART0SEL_HIRC, CLK_CLKDIV0_UART0(1)); / Select UART1 clock source is HIRC / CLK_SetModuleClock(UART1_MODULE, CLK_CLKSEL1_UART1SEL_HIRC, CLK_CLKDIV0_UART1(1)); / Select PCLK1 as the clock source of SPI0 / CLK_SetModuleClock(SPI0_MODULE, CLK_CLKSEL2_SPI0SEL_PCLK1, MODULE_NoMsk); / Enable UART0 peripheral clock / CLK_EnableModuleClock(UART0_MODULE); / Enable UART1 peripheral clock / CLK_EnableModuleClock(UART1_MODULE); / Enable PDMA module clock / CLK_EnableModuleClock(PDMA_MODULE); / Enable SPI0 peripheral clock / CLK_EnableModuleClock(SPI0_MODULE); / Update System Core Clock / / User can use SystemCoreClockUpdate() to calculate PllClock, SystemCoreClock and CycylesPerUs automatically. / SystemCoreClockUpdate(); /---------------------------------------------------------------------------------------------------------/ / Init I/O Multi-function / /---------------------------------------------------------------------------------------------------------/ / Set PB multi-function pins for UART0 RXD=PB.12 and TXD=PB.13 / SYS->GPB_MFPH = (SYS->GPB_MFPH & ~(SYS_GPB_MFPH_PB12MFP_Msk | SYS_GPB_MFPH_PB13MFP_Msk)) | \ (SYS_GPB_MFPH_PB12MFP_UART0_RXD | SYS_GPB_MFPH_PB13MFP_UART0_TXD); / Set PB multi-function pins for UART1 RXD(PB.2) and TXD(PB.3) / SYS->GPB_MFPL = (SYS->GPB_MFPL & ~(SYS_GPB_MFPL_PB2MFP_Msk | SYS_GPB_MFPL_PB3MFP_Msk)) | \ (SYS_GPB_MFPL_PB2MFP_UART1_RXD | SYS_GPB_MFPL_PB3MFP_UART1_TXD); / Setup SPI0 multi-function pins / / PA.3 is SPI0_SS, PA.2 is SPI0_CLK, PA.1 is SPI0_MISO, PA.0 is SPI0_MOSI/ SYS->GPA_MFPL = (SYS->GPA_MFPL & ~(SYS_GPA_MFPL_PA3MFP_Msk | SYS_GPA_MFPL_PA2MFP_Msk | SYS_GPA_MFPL_PA1MFP_Msk | SYS_GPA_MFPL_PA0MFP_Msk)) | (SYS_GPA_MFPL_PA3MFP_SPI0_SS | SYS_GPA_MFPL_PA2MFP_SPI0_CLK | SYS_GPA_MFPL_PA1MFP_SPI0_MISO | SYS_GPA_MFPL_PA0MFP_SPI0_MOSI); / Lock protected registers / SYS_LockReg();}依据新塘的代码风格,系统的初始化被安排在SYS_Init()中完成所有的系统时钟和各个外设时钟的初始化,在初始化时钟之后初始化外设的引脚,设置各种复用,这里初始化了UART0 和 UART1的外设以及SPI0的外设。由于采取自动的NSS,以是这里将NSS的引脚也复用。
SPI初始化
void SPI_Init(void){ /---------------------------------------------------------------------------------------------------------/ / Init SPI / /---------------------------------------------------------------------------------------------------------/ / Configure as a master, clock idle low, 32-bit transaction, drive output on falling clock edge and latch input on rising edge. / / Set IP clock divider. SPI clock rate = 2 MHz / SPI_Open(SPI0, SPI_MASTER, SPI_MODE_0, 8, SPI_CLK_FREQ); / Enable the automatic hardware slave select function. Select the SS pin and configure as low-active. / SPI_EnableAutoSS(SPI0, SPI_SS, SPI_SS_ACTIVE_LOW);}SPI的初始化依旧是两个函数。第一个SPI_Open(SPI_T spi, uint32_t u32MasterSlave, uint32_t u32SPIMode, uint32_t u32DataWidth, uint32_t u32BusClock));内置5个参数,SPI_T spi:选定开启的SPI口。我们选择SPI0。uint32_t u32MasterSlave:选择主模式或者从模式。这里选择主模式。uint32_t u32SPIMode:选择SPI的模式,这里有4个待选参数,紧张是确定SPI的空闲电平,在上升沿还是低落沿读取数据,在第几个沿读数据等,这个在每个MCU都有该配置,只是M031这里做了一个凑集。下图为四个可选参数。
下面给一张掌握SPI设备的各个寄存器值设定图,
我这里选择SPI_MODE_0,即:将SPI时钟的空闲状态设为低电平,选择数据在SPI总线时钟的低落沿传输,选择数据在SPI总线时钟的上升沿锁存。uint32_t u32DataWidth:为数据宽度,可在8-32位之间选择,我这里选择最低的8位,这个是比较常见的数据。uint32_t u32BusClock:设置SPI的时钟,最大24MHz。第二个函数为SPI_EnableAutoSS(SPI0, SPI_SS, SPI_SS_ACTIVE_LOW);设置硬件使能,自动从机选择功能。如果无需自动从选,可利用SPI_DisableAutoSS(SPI0);进行屏蔽,此时可任选一个通用IO掌握从选旗子暗记。
SPI读写数据:
/ Write to TX register / SPI_WRITE_TX(SPI0, 0x50650F); / Check SPI0 busy status / while(SPI_IS_BUSY(SPI0)); i32Err = SPI_READ_RX(SPI0);SPI_WRITE_TX()进行SPI数据的发送,在等待发送完成后用SPI_READ_RX()进行读数据便可完成一次完全的SPI数据读写。
PDMA设置
void SPI_DMAInit(void){ / Reset PDMA module / SYS_ResetModule(PDMA_RST); / Enable PDMA channels / PDMA_Open(PDMA, SPI_OPENED_CH); /======================================================================= SPI master PDMA TX channel configuration: ----------------------------------------------------------------------- Word length = 32 bits Transfer Count = DATA_COUNT Source = g_au32MasterToSlaveTestPattern Source Address = Incresing Destination = SPI0->TX Destination Address = Fixed Burst Type = Single Transfer =========================================================================/ / Set transfer width (32 bits) and transfer count / PDMA_SetTransferCnt(PDMA, SPI_MASTER_TX_DMA_CH, PDMA_WIDTH_8, DATA_COUNT); / Set source/destination address and attributes / PDMA_SetTransferAddr(PDMA, SPI_MASTER_TX_DMA_CH, (uint32_t)SPI_TX, PDMA_SAR_INC, (uint32_t)&SPI0->TX, PDMA_DAR_FIX); / Set request source; set basic mode. / PDMA_SetTransferMode(PDMA, SPI_MASTER_TX_DMA_CH, PDMA_SPI0_TX, FALSE, 0); / Single request type. SPI only support PDMA single request type. / PDMA_SetBurstType(PDMA, SPI_MASTER_TX_DMA_CH, PDMA_REQ_SINGLE, 0); / Disable table interrupt / PDMA->DSCT[SPI_MASTER_TX_DMA_CH].CTL |= PDMA_DSCT_CTL_TBINTDIS_Msk; /======================================================================= SPI master PDMA RX channel configuration: ----------------------------------------------------------------------- Word length = 32 bits Transfer Count = DATA_COUNT Source = SPI0->RX Source Address = Fixed Destination = g_au32MasterRxBuffer Destination Address = Increasing Burst Type = Single Transfer =========================================================================/ / Set transfer width (32 bits) and transfer count / PDMA_SetTransferCnt(PDMA, SPI_MASTER_RX_DMA_CH, PDMA_WIDTH_8, DATA_COUNT); / Set source/destination address and attributes / PDMA_SetTransferAddr(PDMA, SPI_MASTER_RX_DMA_CH, (uint32_t)&SPI0->RX, PDMA_SAR_FIX, (uint32_t)SPI_RX, PDMA_DAR_INC); / Set request source; set basic mode. / PDMA_SetTransferMode(PDMA, SPI_MASTER_RX_DMA_CH, PDMA_SPI0_RX, FALSE, 0); / Single request type. SPI only support PDMA single request type. / PDMA_SetBurstType(PDMA, SPI_MASTER_RX_DMA_CH, PDMA_REQ_SINGLE, 0); / Disable table interrupt / PDMA->DSCT[SPI_MASTER_RX_DMA_CH].CTL |= PDMA_DSCT_CTL_TBINTDIS_Msk; / Enable SPI master DMA function / SPI_TRIGGER_TX_PDMA(SPI0); SPI_TRIGGER_RX_PDMA(SPI0);}这里PDMA设置和UART差不多,不罗里吧嗦的胡扯了,紧张便是一个SPI的使能,这里用SPI_TRIGGER_TX_PDMA()和SPI_TRIGGER_RX_PDMA()。这个是UART的SPI没有的。
在这个初始化完成后会进行一次发送和吸收,如果不须要请删除 SPI_TRIGGER_TX_PDMA(SPI0); SPI_TRIGGER_RX_PDMA(SPI0);如果想要重复PDMA 的发送可做如下处理:
/ Re-trigger / / Master PDMA TX channel configuration / / Set transfer width (32 bits) and transfer count / PDMA_SetTransferCnt(PDMA, SPI_MASTER_TX_DMA_CH, PDMA_WIDTH_32, DATA_COUNT); / Set request source; set basic mode. / PDMA_SetTransferMode(PDMA, SPI_MASTER_TX_DMA_CH, PDMA_SPI0_TX, FALSE, 0); / Master PDMA RX channel configuration / / Set transfer width (32 bits) and transfer count / PDMA_SetTransferCnt(PDMA, SPI_MASTER_RX_DMA_CH, PDMA_WIDTH_32, DATA_COUNT); / Set request source; set basic mode. / PDMA_SetTransferMode(PDMA, SPI_MASTER_RX_DMA_CH, PDMA_SPI0_RX, FALSE, 0); / Enable master's DMA transfer function / SPI_TRIGGER_TX_PDMA(SPI0); SPI_TRIGGER_RX_PDMA(SPI0);可以封装为一个函数,每次须要启动DMA就调用一次该函数。但是这里又是老问题,启动DMA的速率慢,示波器测试须要8个微秒旁边吧,韶光过长。同样我们可以混搭寄存器操作,加快代码实行效率,这个技巧非常实用;
void SPI_DMA_WRITE(void){ PB1 = 0; / Master PDMA TX channel configuration / PDMA->CHCTL |= (1 << SPI_MASTER_TX_DMA_CH); / Enable PDMA channel / PDMA->DSCT[SPI_MASTER_TX_DMA_CH].CTL = (PDMA->DSCT[SPI_MASTER_TX_DMA_CH].CTL & (~(PDMA_DSCT_CTL_TXCNT_Msk | PDMA_DSCT_CTL_OPMODE_Msk))) | (DATA_COUNT - 1) << PDMA_DSCT_CTL_TXCNT_Pos | / Transfer count / 1 << PDMA_DSCT_CTL_OPMODE_Pos; / Master PDMA RX channel configuration / PDMA->CHCTL |= (1 << SPI_MASTER_RX_DMA_CH); / Enable PDMA channel / PDMA->DSCT[SPI_MASTER_RX_DMA_CH].CTL = (PDMA->DSCT[SPI_MASTER_RX_DMA_CH].CTL & (~(PDMA_DSCT_CTL_TXCNT_Msk | PDMA_DSCT_CTL_OPMODE_Msk))) | (DATA_COUNT - 1) << PDMA_DSCT_CTL_TXCNT_Pos | / Transfer count / 1 << PDMA_DSCT_CTL_OPMODE_Pos; / Enable master's DMA transfer function / SPI0->PDMACTL = (SPI_PDMACTL_RXPDMAEN_Msk | SPI_PDMACTL_TXPDMAEN_Msk); while (PDMA->DSCT[SPI_MASTER_TX_DMA_CH].CTL & PDMA_DSCT_CTL_TXCNT_Msk) ; PB1 = 1;}这样,M031的DMA就完成了,如果须要启动DMA传输,就调用SPI_DMA_WRITE()函数便可。
原标题:【又换MCU】越来越好用系列,新塘031 SPI PDMA通信
原作者:呐咯密密
本文为21ic有奖征文作品,详情请见21ic论坛活动专区:第二届万元红包——蓝V达人有奖征文活动,如果您也有兴趣参与征文,欢迎进入论坛参与活动~










