1.上一篇文章介绍的是PWM+DMA发送数据,这次来一个SPI+DMA发送数据的方式,巩固一下STM32的知识,之前没使用过STM32的SPI驱动方式,DMA也很少使用,不过DMA使用起来确实好,不占用CPU的资源,直接内存到外设。本次实验STM32F103的SPI+DMA功能,实验的模块还是WS2812灯带。
2
.STM32C8T6基本的资源介绍:
STM32F103x8和STM32F103xB增强型系列使用高性能的ARM® Cortex™-M3 32位的RISC内核,工
作频率为72MHz,内置高速存储器(高达128K字节的闪存和20K字节的SRAM),丰富的增强I/O端口
和联接到两条APB总线的外设。所有型号的器件都包含2个12位的ADC、3个通用16位定时器和1个
PWM定时器,还包含标准和先进的通信接口:多达2个I
2
C接口和SPI接口、3个USART接口、一个
USB接口和一个CAN接口。
3.
WS2812B:就是一个RGB灯带,只给信号,就量相应的颜色,理论上像素255*255*255种颜色,主要实验3原色显示。
4.下面是STM32F103配置SPI+DMA的方式,使用的标准库。我使用的是PA7,SPI_1,因为是驱动的是WS2812B灯带,主需要用到SPI1的MOSI引脚就行,不要配置其他的。再看一下DMA1对应SPI1_TX的通道,通过手册可以看出SPI1_TX在DMA1的3通道上。因为WS2812B的最大传输速度是800KHZ,所以SPI的速度要设置合理,不然数据可能会出错,导致灯带的颜色和顺序不一样。SPI1挂在的APB2上,系统时钟默认是72M,SPI_BaudRatePrescaler_8 八分频后是9M,发送一位的时间是111nu.然后对照一下WS2812B的0码和1码的时序图。
1码:1111 1110 =0x0fe 777ns
0码: 1110 000 =0xe0 333ns
第1个WS2812B灯珠接收到了第1个24bit的数据,做出响应(发光);第N个WS2812B灯珠接收到了第N个24bit的数据,
再收到第二个24bit的数据后,直接转发给第二个WS2812B灯珠,由第二个WS2812B灯珠做出响应;依次类推。
下面是STM32F103配置SPI1+DMA1的代码
void Ws2812b_SPI_Dma_GpioConfig(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
RCC_APB2PeriphClockCmd(WS2812B_SPIx_GPIO_CLK, ENABLE); //PORTA时钟使能
RCC_APB2PeriphClockCmd(WS2812B_SPIx_CLK, ENABLE); //SPI1时钟使能
RCC_AHBPeriphClockCmd(WS2812B_SPIx_DMAx_CLK, ENABLE); //使能DMA传输
/* PA7 SPI1_MOSI */
GPIO_InitStructure.GPIO_Pin = WS2812B_SPIx_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //PA7复用推挽输出 SPI
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(WS2812B_SPIx_PORT, &GPIO_InitStructure);//初始化GPIOA
SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx; //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置SPI工作模式:设置为主SPI
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //设置SPI的数据大小:SPI发送接收8位帧结构
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //串行同步时钟的空闲状态为低电平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //串行同步时钟的第2个跳变沿(上升或下降)数据被采样
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8; //定义波特率预分频的值:波特率预分频值为9m
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC值计算的多项式
SPI_Init(WS2812B_SPIx, &SPI_InitStructure); //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器
SPI_Cmd(WS2812B_SPIx, ENABLE); //使能SPI外设
SPI_I2S_DMACmd(WS2812B_SPIx, SPI_I2S_DMAReq_Tx, ENABLE);//启用SPI发送DMA请求
DMA_DeInit(WS2812B_SPIx_DMAx_Channelx); //将DMA的通道1寄存器重设为缺省值
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) &(WS2812B_SPIx -> DR); //cpar; //DMA外设ADC基地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)Pixel_Buff; //cmar; //DMA内存基地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //数据传输方向,从内存读取发送到外设
DMA_InitStructure.DMA_BufferSize = PIXEL_NUM * RGB; //DMA通道的DMA缓存的大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址寄存器递增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //数据宽度为8位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度为8位
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //工作在正常缓存模式
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA通道 x拥有中优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输
DMA_Init(WS2812B_SPIx_DMAx_Channelx, &DMA_InitStructure); //根据DMA_InitStruct中指定的参数初始化DMA的通道USART1_Tx_DMA_Channel所标识的寄存器
}
下面是驱动WS2812灯带的代码:
//缓存数据
u8 Pixel_Buff[PIXEL_NUM][RGB]={0};
void Ws281b_Show(void)
{
DMA_SetCurrDataCounter(WS2812B_SPIx_DMAx_Channelx, (u16)(PIXEL_NUM * RGB)); //设定DMA要传输数据的大小
DMA_Cmd(WS2812B_SPIx_DMAx_Channelx, ENABLE);
while(DMA_GetFlagStatus(WS2812B_SPIx_DMA_FLAG) != SET); //等待传输完成
DMA_ClearFlag(WS2812B_SPIx_DMA_FLAG);
DMA_Cmd(WS2812B_SPIx_DMAx_Channelx, DISABLE);
WS2812B_PIN_L;
Delay_us(350);
}
void Ws2812b_CloseAll(void)//熄灭
{
u16 i;
u8 j;
for(i = 0; i < PIXEL_NUM; ++i)
{
for(j = 0; j < 24; ++j)
{
Pixel_Buff[i][j] = T0H;
// printf("%x",Pixel_Buff[i][j]);
}
// printf("\r\n");
}
Ws281b_Show(); //传输数据
}
/**
* @brief 将RGB颜色组合到一个32位数据中存储
* @param red: 0-255
* green:0-255
* blue: 0-255
* @retval None
*/
u32 ws281x_color(u8 r, u8 g, u8 b)
{
return r << 16 | g << 8 | b;
}
/**
* @brief 给特定LED灯设定颜色
* @param n:LED灯号
* GRBClor: 32位的颜色值
* @retval None
*/
void ws281x_setPixelColor(u16 n, uint32_t GRBColor)
{
u8 i;
if(n < PIXEL_NUM)
{
for(i = 0; i < RGB; i++)
{
Pixel_Buff[n][i] = ((GRBColor << i) & 0x800000) ? T1H : T0H;
//printf(" %x ",Pixel_Buff[n][i]);
}
// printf("\r\n");
}
Ws281b_Show(); //传输数据
}
把数据填充到数组里面,然后启动DMA发送信息就行了。我也是第一次使用改模式,有很多细节需要自己摸索。