-
最近需要用华大的hc32l136的硬件SPI+DMA传输,瞎写很久没调好,看参考手册,瞎碰一天搞通了。。。
- 先说下我之前犯的错误,也是最宝贵的经验,供参考
- 没多看参考手册直接写(即使有点烂仍然提供了最高的参考价值。。。),重点看
SPI
和DMAC
章节 - 错误使用了
软件触发传输
,测到的现象是前两个字节可以正确发送,后面的无论是发送数量和数据都对不上了,误以为软件触发可用,自己的配置有问题,实际测试软件触发和规格书讲的一样,是不可用的或者说是不可靠的
- 没多看参考手册直接写(即使有点烂仍然提供了最高的参考价值。。。),重点看
- 再说下正确的使用方式,文末会粘上测试代码
- 关键点就一个,触发方式
不要选DmaSWTrig软件触发
(至于最后实例能用的这个,从软件的角度看还是软件触发,但官方的角度似乎不认为这是软件触发,或许是软件触发SPI硬件再触发DMA所以叫硬件触发?不管也罢,,,)
- 关键点就一个,触发方式
- 先说下我之前犯的错误,也是最宝贵的经验,供参考
-
特别注意
:DMA可能存在硬件bug,在发送的最后一个字节DMA发送完成标记位(硬件置位)有50%概率提前置位,如果CS拉高太快(优化等级太高并且直接操作寄存器来操作CS)会概率导致最后一个字节异常,可以在拉高CS前加一个很小的延时解决,我单独测试DMA时没有异常,随便打开一个定时器中断(频率越高越容易)就可以碰到了,错误时序如下:
-
语文课兴许没及格,下面是参考手册的一些相关描述,没看明白:
- 先讲SPI支持软硬件访问
2.再讲只支持硬件块传输模式,且SPI和系统时钟不同频时不支持硬件触发(官方对软件/硬件触发的解释不是很到位,至少和我理解的不太一样)
- 但是spi时钟和系统时钟必然是不同频的,那硬件触发到底能不能用呢?
- 再看,所谓的软件/硬件DMA传输模式就是软件/硬件请求方式不同,似乎哪个也不支持了。。。软硬件触发和软硬件传输似乎没有关系?
- 最后,还是实践出真知。。。
- 测试程序参考,每200ms用SPI+DMA发送24个字节:
#define SPI_HANDLE M0P_SPI1
#define DMA_HANDLE DmaCh1
uint8_t data_tx_test[24] =
{
0x11,0x22,0x23,0x44,0x55,0x66,0x77,0x88,
0x11,0x22,0x23,0x44,0x55,0x66,0x77,0x88,
0x11,0x22,0x23,0x44,0x55,0x66,0x77,0x88,
};
//主要是CS/CLK/MOSI三个脚,不相干引脚忽略即可
static void App_GpioInit(void)
{
stc_gpio_cfg_t stcPortCfg;
DDL_ZERO_STRUCT(stcPortCfg); //结构体初始化清零
Sysctrl_SetPeripheralGate(SysctrlPeripheralGpio, TRUE); //GPIO 外设时钟使能
stcPortCfg.enDrv = GpioDrvH;
stcPortCfg.enDir = GpioDirOut;
Gpio_Init(LCD_BK_PORT, LCD_BK_PIN, &stcPortCfg);
Gpio_Init(LCD_CS_PORT, LCD_CS_PIN, &stcPortCfg);
Gpio_SetAfMode(LCD_CS_PORT, LCD_CS_PIN,GpioAf1); //CS
Gpio_Init(LCD_RESET_PORT, LCD_RESET_PIN, &stcPortCfg);
Gpio_Init(LCD_WR_PORT, LCD_WR_PIN, &stcPortCfg);
Gpio_Init(LCD_SCK_PORT, LCD_SCK_PIN, &stcPortCfg);
Gpio_SetAfMode(LCD_SCK_PORT, LCD_SCK_PIN,GpioAf1); //CLK
Gpio_Init(LCD_SDA_PORT, LCD_SDA_PIN, &stcPortCfg);
Gpio_SetAfMode(LCD_SDA_PORT, LCD_SDA_PIN,GpioAf1); //MOSI
}
static void App_SPIInit(void)
{
stc_spi_cfg_t SpiInitStruct;
Sysctrl_SetPeripheralGate(SysctrlPeripheralSpi1,TRUE);
//SPI0模块配置:主机
SpiInitStruct.enSpiMode = SpiMskMaster; //配置位主机模式
SpiInitStruct.enPclkDiv = SpiClkMskDiv2; //波特率:fsys/2
SpiInitStruct.enCPOL = SpiMskcpolhigh; //极性
SpiInitStruct.enCPHA = SpiMskCphasecond; //第二电平采样
Spi_Init(SPI_HANDLE, &SpiInitStruct);
Spi_FuncEnable(SPI_HANDLE,SpiMskDmaTxEn); //这里只使用了发送功能
}
static void App_DmaCfg(void)
{
stc_dma_cfg_t stcDmaCfg;
Sysctrl_SetPeripheralGate(SysctrlPeripheralDma,TRUE); //打开DMA时钟
DDL_ZERO_STRUCT(stcDmaCfg);
stcDmaCfg.enMode = DmaMskBlock; //选择块传输
stcDmaCfg.u16BlockSize = 1; //块传输个数
stcDmaCfg.u16TransferCnt = 24; //块传输次数,一次传输数据大小为 块传输个数*BUFFER_SIZE
stcDmaCfg.enTransferWidth = DmaMsk8Bit; //传输数据的宽度,此处选择字(8Bit)宽度
stcDmaCfg.enSrcAddrMode = DmaMskSrcAddrInc; //源地址自增
stcDmaCfg.enDstAddrMode = DmaMskDstAddrFix; //目的地址固定
stcDmaCfg.enDestAddrReloadCtl = DmaMskDstAddrReloadEnable; //使能重新加载传输目的地址
stcDmaCfg.enSrcAddrReloadCtl = DmaMskSrcAddrReloadEnable; //使能重新加载传输源地址
stcDmaCfg.enSrcBcTcReloadCtl = DmaMskBcTcReloadEnable; //使能重新加载BC/TC值
stcDmaCfg.u32SrcAddress = (uint32_t)&data_tx_test[0]; //指定传输源地址
stcDmaCfg.u32DstAddress = (uint32_t)&(M0P_SPI1->DATA); //指定传输目的地址
stcDmaCfg.enRequestNum = DmaSPI1TXTrig; //设置为硬件触发
stcDmaCfg.enTransferMode = DmaMskOneTransfer; //dma只传输一次,DMAC传输完成时清除CONFA:ENS位
stcDmaCfg.enPriority = DmaMskPriorityFix; //各通道固定优先级,CH0优先级 > CH1优先级
Dma_InitChannel(DMA_HANDLE,&stcDmaCfg); //初始化dma通道0
Dma_Enable();
//Dma_EnableChannel(DMA_HANDLE); //开启通道即开启一次发送
}
void dma_test(void)
{
en_dma_stat_t ste;
while(1)
{
delay1ms(200);
M0P_SPI1->SSN = FALSE;
Dma_EnableChannel(DMA_HANDLE); //启动传输,所以这种方式到底算软件还是硬件??
ste = Dma_GetStat(DMA_HANDLE);
while(ste != DmaTransferComplete)
{
ste = Dma_GetStat(DMA_HANDLE);
}
delay10us(1); //!!!这个延时是必要的,防止数据多时上面的等待不能有效判断最后一个字节
M0P_SPI1->SSN = TRUE;
}
}
void demo(void)
{
App_GpioInit();
App_DmaCfg();
App_SPIInit();
dma_test();
}
- 实测SPI主机发送ok: