【前言】
准备使用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的大佬。
我要赚赏金
