【前言】
准备使用GD32F527的SPI来实现WS2812的驱动,由于SPI只需要MOSI,但是为了方便观察数据,还需要SCK。这一篇只需要MOSI通过DMA数据输出就行,不需要CS、MISO。
GD32F5xx在示例中没有SPI的示例,因此需要自己摸索进行SPI总线的配置与DMA的配置,经过很长时间的摸索,实现了SPI+DMA的数据传送。
【资料准备与IO的分配】
1、GD32F527有一个UART+DMA的示例,从其中学习DMA的配置的参照。
2、GD32F5xx的数据手册。在数据手册中,首先找到与开发板引出的IO进行配置,通过阅读,找到了可用的IO:
使用PC1为SPI1的MOSI
数据手册的PC1的IO复用:
3、找到SPI1的SCK为PA9,在开发板上有引出:
数据手册的PA9的复用:
4、分配好IO后,我们还需要找到AF复用通道,在数据手册中找到复用表:
PC1复用为AF7
PA9为AF5
6、DMA0通道与外设通道,在DMA配置中需要用到这两个参数,在它的数据手册中有找到
找到SPI1_TX是DMA0,CH4,同时外设通道为0
到此资料准备完毕,下面准备配置SPI以及DMA。
【SPI1初始化配置】
我们使用到的外设时钟有GPIOA、GPIOC、SPI1四个时钟。
void spi_config(void) { spi_parameter_struct spi_init_struct; rcu_periph_clock_enable(RCU_GPIOA); rcu_periph_clock_enable(RCU_GPIOC); rcu_periph_clock_enable(RCU_SPI1); // SPI1_SCK PA9 AF5 SPI1_MOSI PC1 AF7 gpio_af_set(GPIOC, GPIO_AF_7, GPIO_PIN_1); gpio_mode_set(GPIOC, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_1); gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_10MHZ, GPIO_PIN_1); gpio_af_set(GPIOA, GPIO_AF_5, GPIO_PIN_9); gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_9); gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_10MHZ, GPIO_PIN_9); // SPI 参数初始化 spi_init_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX; // 全双工模式 spi_init_struct.device_mode = SPI_MASTER; // 主机模式 spi_init_struct.frame_size = SPI_FRAMESIZE_8BIT; // 数据帧长度为 8 位 spi_init_struct.clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE; // 时钟极性和相位 spi_init_struct.nss = SPI_NSS_SOFT; // 软件控制 NSS spi_init_struct.prescale = SPI_PSC_64; // 时钟分频 spi_init_struct.endian = SPI_ENDIAN_MSB; // 高位优先 spi_init(SPI1, &spi_init_struct); // 初始化 SPI1 spi_enable(SPI1); // 启用 SPI }
2、初始化后,编写一个测试函数:
// 发送数据 void spi_send_data(uint16_t data) { while (RESET == spi_i2s_flag_get(SPI1, SPI_FLAG_TBE)); // 等待发送缓冲区空 spi_i2s_data_transmit(SPI1, data); // 写入数据 }
在main中执行发送一个0xEE进行观察
使用逻辑分析仪,捕获时序,频率为775KHz基于能满足WS2812的传输时序:
使用SPI发送正常后,下面进行DMA的配置
【SPI-DMA配置】
void usart_dma_config(void) { dma_single_data_parameter_struct dma_init_struct; /* enable DMA0 */ rcu_periph_clock_enable(RCU_DMA0); /* deinitialize DMA channel4(SPI1 TX) */ dma_deinit(DMA0, DMA_CH4); dma_init_struct.direction = DMA_MEMORY_TO_PERIPH; dma_init_struct.memory0_addr = (uint32_t)tx_buffer; dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE; dma_init_struct.number = 24; dma_init_struct.periph_addr = (uint32_t)&SPI_DATA(SPI1);; dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE; dma_init_struct.periph_memory_width = DMA_PERIPH_WIDTH_8BIT; dma_init_struct.priority = DMA_PRIORITY_LOW; dma_single_data_mode_init(DMA0, DMA_CH4, &dma_init_struct); dma_channel_subperipheral_select(DMA0, DMA_CH4, DMA_SUBPERI0); /* configure DMA mode */ dma_circulation_disable(DMA0, DMA_CH4); dma_channel_disable(DMA0,DMA_CH4); } // 启动DMA发送函数 void spi1_dma_send(uint8_t *data, uint16_t length) { // 清除DMA通道 dma_single_data_parameter_struct dma_init_struct; dma_channel_disable(DMA0, DMA_CH4); // 设置发送数据地址和长度 dma_init_struct.direction = DMA_MEMORY_TO_PERIPH; dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE; dma_init_struct.memory0_addr = (uint32_t)data; dma_init_struct.number = length; dma_init_struct.periph_addr = (uint32_t)&SPI_DATA(SPI1);; dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE; dma_init_struct.periph_memory_width = DMA_PERIPH_WIDTH_8BIT; dma_init_struct.priority = DMA_PRIORITY_LOW; dma_single_data_mode_init(DMA0, DMA_CH4, &dma_init_struct); // 清除DMA标志 dma_flag_clear(DMA0, DMA_CH4, DMA_FLAG_FTF); dma_circulation_disable(DMA0, DMA_CH4); // 启动DMA传输 dma_channel_enable(DMA0, DMA_CH4); spi_dma_enable(SPI1, SPI_DMA_TRANSMIT); while(!dma_flag_get(DMA0,DMA_CH4, DMA_FLAG_FTF)); }
在配置中,我们使用DMA0 CH4进行配置,数据方向为内存到外设,数据宽度为8BIT。
特别注意的是,在使能dma传输时,需要使用spi_dma_enable对SPI的DMATEN位进行置1,来启用。在数据手册中有描述如下:
【测试效果】
测试代码如下:
int main(void) { uint8_t dat[] = {0x11,0x33}; systick_config(); spi_config(); usart_dma_config(); spi_send_data(0xEE); spi1_dma_send(&dat[0], 2); while(!spi1_dma_tx_complete()) { ; } while(1) { /* turn on led1, turn off led4 */ } }
通过逻辑分析仪的数据捕获,如期实现其功能:
【总结】
由于没有SPI+DMA的示例,只能通过阅读用户手册,慢慢的进行摸索,希望这篇文章能帮助到在需要使用SPI_TX+DMA的大佬。