本帖最后由 袁胜富 于 2023-1-2 21:35 编辑
一、前言
首先非常感谢雅特力提供的开发板,基于此我本人发表此篇文章。
二、项目介绍
AT32F437主频高达288MHZ和大容量的Flash和SRAM,非常适合跑操作系统,在操作系统上我们可以做很多复杂的应用逻辑,其优势远远超过了裸机的轮询方法。因此我特别在AT32F437上跑TencentOS-tiny这个开源操作系统。在此篇文章中我只在系统上跑一个任务,运行WS2812,让其跑出不同模式。
三、WS2812的认识
Features and Benefits
The control circuit and the LED share the only power source.
Control circuit and RGB chip are integrated in a package of 5050 components, to form a complete addressable
pixel.
Built-in signal reshaping circuit, after wave reshaping to the next driver, ensure wave-form distortion not
accumulate.
Built-in electric reset circuit and power lost reset circuit.
Each pixel of the three primary color can achieve 256 brightness display, completed 16777216 color full color
display, and scan frequency is of 2KHz.
Cascading port transmission signal by single line.
Any two point the distance not more than 5m transmission signal without any increase circuit.
When the refresh rate is 30fps, cascade number are not less than 1024 pixels.
Send data at speeds of 800Kbps.
The color of the light is highly consistent, cost-effective.
No need for external electric components and even the capacitor.
以上是数据手册摘抄下来的,然后来看看WS2812的数据格式
数据传输时间
数据序列和级联法
数据传输以及数据组成方式
通过以上资料我们得知一个灯的数据由24bit组成,GRB中G占位 ,R占8位,B占8位,G代表绿色,R代表红色,B代表蓝色,通过三种颜色可以显示出我们要的颜色。
WS2812B的数据传输速率位800K,1/8000000=1.25us。通过数据手册得知,0码的T0H时间为220ns~380ns,T0L时间为580ns~1us.1码的T1H时间为580ns~1us,T1L时间为220ns~380ns。WS2812使用方法一般为级联使用,由于本身拥有锁存器所以数据流经D1然后流经D2.。。。。
四、AT32F437的SPI和DMA理论
在此应用中我们只用到SPI的MOSI引脚,故SPI的工作模式设置为发送模式就行了,通过查阅芯片的Datasheet知道此芯片只用一个SPI为SPI1,MOSI为PA7引脚。
大家务必记住在使用SPI的时候一定要注意CS_SOFT模式是CS的内部电平应该置为高,只有这样SPI才能工作在主机模式,也就是MASTER Mode。
通过芯片手册知道SPI1挂载在APB2总线。在程序中配置AHB为288MHZ ,APB2为144MHZ。空口无凭请看下图
DMA分为DMA1和DMA2,在手册中了解到AT32F437的DMA很不错,通过DMAMux可以将任务DMAx通道连接任意不同外设,此外带有EDMA增强型DMA,这里只讲普通的DMA使用。DMA框架图如下
DMAMUX,DMA的多路复用框图如下
五、理论总结
要将系统时钟设置为144M,SPI1时钟分频数设置为16,则SPI的通信频率为9M,1s/9M≈111ns 即传输一位数据的时间约为133纳秒(ns)8*111 =88ns 符合WS2812。
2*111= 222ns 6*111 = 666ns 符合WS281X芯片的通信时序。
1111 1100high level (十六进制:0XFC)表示WS281X的1码
1100 0000 low level (十六进制:0XC0)表示WS281X的0码 SPI1的模式本项目只用发送模式,也就说只用到MOSI,没用到CS,MISO和SCK信号线。其中MOSI连接到PA7,在IO配置时应该选择复用模式,通过GPIO的复用表(如下图所示),知道PA7选择复用5才能将SPI1的MOSI功能体现。
六、程序编写
1.spi_dma_ws2812.h 头文件代码展示,在文件中#define SPI_DMA_IRQ 1这个宏可以选择DMA传输数据失否需要DMA中断参与
#ifndef __SPI_DMA_WS2812_H
#define __SPI_DMA_WS2812_H
#include "stdlib.h"
#include "at32f435_437_board.h"
#include "stdint.h"
#include "tos_k.h"
/*
要将系统时钟设置为144M,SPI分频数设置为16,则SPI的通信频率为9M,1s/9M≈111ns 即传输一位数据的时间约为133纳秒(ns)8*111 =88ns 符合WS2812
2*111= 222ns 6*111 = 666ns 符合WS281X芯片的通信时序。
1111 1100high level (十六进制:0XFC)表示WS281X的1码
1100 0000 low level (十六进制:0XC0)表示WS281X的0码
程序头文件部分: 通过宏的方式定义了灯珠个数和WS281X的0码和1码。
*/
// _____
// | |___| 1111 1100 high level
// ___
// | |_____| 1100 0000 low level
#define SPI_DMA_IRQ 1
#define Delay_ms(wait) tos_task_delay(wait)
#define WS2812B //选择WS2812B
#define PIXEL_NUM1 2 //RGB灯的数量 //灯带1灯数(组数)
#define PIXEL_NUM2 2 //灯带2灯数(组数)
#define PIXEL_NUM3 2 //灯带3灯数(组数)
#define PIXEL_NUM4 2 //灯带4灯数(组数)
#define PIXEL_NUM (PIXEL_NUM1+PIXEL_NUM2+PIXEL_NUM3+PIXEL_NUM4)//灯数统计
#define GRB 24 //3*8 //Red8位 Green8位 Blue8位 加起来24位
#ifdef WS2812B
#define WS_HIGH 0xFC
#endif
#define WS_LOW 0xC0
/*
R G B
红: 255 0 0
黄: 255 255 0
绿: 0 255 0
青: 0 255 255
蓝: 0 0 255
紫: 255 0 255
白: 255 255 255
*/
typedef union _rgbPixelBuffer
{
struct
{
uint8_t PixelBuffer1[PIXEL_NUM1][GRB];
uint8_t PixelBuffer2[PIXEL_NUM2][GRB];
uint8_t PixelBuffer3[PIXEL_NUM3][GRB];
uint8_t PixelBuffer4[PIXEL_NUM4][GRB];
}buff;
uint8_t All_Buffer[PIXEL_NUM][GRB];
}RGB_PixelBuffer,*PRGB_PixelBuffer;
void WS281x_Init(void);
void WS281x_CloseAll(void);
uint32_t WS281x_Color(uint8_t red, uint8_t green, uint8_t blue);
void WS281x_SetPixelColor(uint16_t n, uint32_t GRBColor);
void WS281x_SetPixelRGB(uint16_t n ,uint8_t red, uint8_t green, uint8_t blue);
void WS281x_Show(void);
void WS281x_RainbowCycle(uint8_t wait);
void WS281x_TheaterChase(uint32_t c, uint8_t wait);
void WS281x_ColorWipe(uint32_t c, uint8_t wait);
void WS281x_Rainbow(uint8_t wait);
void WS281x_TheaterChaseRainbow(uint8_t wait);
void RGB_DEBUG_TEST(void);
#endif
2.spi_dma_ws2812.c代码展示
#include "spi_dma_ws2812.h"
#include "string.h"
#include "stdlib.h"
#include "stdbool.h"
RGB_PixelBuffer pixelBuffer;
#if SPI_DMA_IRQ
void DMA1_Channel2_IRQHandler(void);
volatile bool app_dma_xfer_done = false; /* DMA xfer done flag. */
#endif
void WS281x_Init(void)
{
/*Set GPIO*/
gpio_init_type gpio_initstructure;
spi_init_type spi_init_struct;
dma_init_type dma_init_struct;
crm_periph_clock_enable(CRM_SPI1_PERIPH_CLOCK, TRUE);
crm_periph_clock_enable(CRM_GPIOA_PERIPH_CLOCK, TRUE);
crm_periph_clock_enable(CRM_DMA1_PERIPH_CLOCK, TRUE);
/* mosi */
gpio_initstructure.gpio_out_type = GPIO_OUTPUT_PUSH_PULL;
gpio_initstructure.gpio_mode = GPIO_MODE_MUX;
gpio_initstructure.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
gpio_initstructure.gpio_pull = GPIO_PULL_UP;
gpio_initstructure.gpio_pins = GPIO_PINS_7;
gpio_init(GPIOA, &gpio_initstructure);
gpio_pin_mux_config(GPIOA, GPIO_PINS_SOURCE7, GPIO_MUX_5);
dma_reset(DMA1_CHANNEL2);
dma_default_para_init(&dma_init_struct);
dma_init_struct.buffer_size = 0;
dma_init_struct.direction = DMA_DIR_MEMORY_TO_PERIPHERAL ;
dma_init_struct.memory_base_addr = (uint32_t)&pixelBuffer;
dma_init_struct.memory_data_width = DMA_MEMORY_DATA_WIDTH_BYTE;
dma_init_struct.memory_inc_enable = TRUE;
dma_init_struct.peripheral_base_addr = (uint32_t)(&SPI1->dt);
dma_init_struct.peripheral_data_width = DMA_PERIPHERAL_DATA_WIDTH_BYTE;
dma_init_struct.peripheral_inc_enable = FALSE;
dma_init_struct.priority = DMA_PRIORITY_VERY_HIGH;
dma_init_struct.loop_mode_enable = FALSE;
dma_init(DMA1_CHANNEL2, &dma_init_struct);
dmamux_enable(DMA1, TRUE);
dmamux_init(DMA1MUX_CHANNEL2, DMAMUX_DMAREQ_ID_SPI1_TX);
spi_i2s_dma_transmitter_enable(SPI1, TRUE);
dma_channel_enable(DMA1_CHANNEL2, TRUE);
while(dma_flag_get(DMA1_FDT2_FLAG) != RESET);
dma_flag_clear(DMA1_FDT2_FLAG);
dma_channel_enable(DMA1_CHANNEL2, FALSE);
spi_i2s_dma_transmitter_enable(SPI1, TRUE);
#if SPI_DMA_IRQ
dma_interrupt_enable(DMA1_CHANNEL2,DMA_FDT_INT,TRUE);
dma_flag_clear(DMA1_FDT2_FLAG);
nvic_irq_enable(DMA1_Channel2_IRQn, 1, 0);
#endif
spi_default_para_init(&spi_init_struct);
spi_init_struct.transmission_mode = SPI_TRANSMIT_HALF_DUPLEX_TX;
spi_init_struct.master_slave_mode = SPI_MODE_MASTER;
spi_init_struct.mclk_freq_division = SPI_MCLK_DIV_16;
spi_init_struct.first_bit_transmission = SPI_FIRST_BIT_MSB;
spi_init_struct.frame_bit_num = SPI_FRAME_8BIT;
spi_init_struct.clock_polarity = SPI_CLOCK_POLARITY_LOW;
spi_init_struct.clock_phase = SPI_CLOCK_PHASE_1EDGE;
spi_init_struct.cs_mode_selection = SPI_CS_SOFTWARE_MODE;
spi_init(SPI1, &spi_init_struct);
spi_crc_polynomial_set(SPI1, 7);
spi_crc_enable(SPI1, TRUE);
spi_software_cs_internal_level_set(SPI1,SPI_SWCS_INTERNAL_LEVEL_HIGHT);//主机模式 SPI_CS_SOFTWARE_MODE SPI_SWCS_INTERNAL_LEVEL_HIGHT
spi_enable(SPI1, TRUE);
WS281x_CloseAll(); //关闭全部的灯
Delay_ms(100); //关闭全部的灯需要一定的时间
}
//更新颜色显示(在设定颜色后将颜色数据存入缓存只有执行该函数后才会进行显示)
void WS281x_Show(void)
{
#if SPI_DMA_IRQ
app_dma_xfer_done = false;
dma_channel_enable(DMA1_CHANNEL2, FALSE);
dma_data_number_set(DMA1_CHANNEL2, PIXEL_NUM * GRB);
dma_channel_enable(DMA1_CHANNEL2, TRUE);
while(!app_dma_xfer_done);
SPI1->dt = 0x00;
#else
// 检查DMA发送通道内是否还有数据
while(dma_data_number_get(DMA1_CHANNEL2)){};
dma_channel_enable(DMA1_CHANNEL2, FALSE);
dma_data_number_set(DMA1_CHANNEL2, PIXEL_NUM * GRB);
dma_channel_enable(DMA1_CHANNEL2, TRUE);
while(dma_data_number_get(DMA1_CHANNEL2)){};
__NOP();
__NOP();
__NOP();
SPI1->dt = 0x00;
while(dma_flag_get(DMA1_FDT2_FLAG) == RESET){};
dma_flag_clear(DMA1_FDT2_FLAG);
dma_channel_enable(DMA1_CHANNEL2, FALSE);
#endif
}
//配置完成之后便可以构思底层控制函数了,为了方便多个LED灯珠的可控制首先要定义一个缓冲区pixelBuffer[PIXEL_NUM][24]\
通过设定颜色将数据填入缓冲区再通过更新函数将数据传入到LED灯珠上。
//关闭所有灯珠
void WS281x_CloseAll(void)
{
uint16_t i;
uint8_t j;
for(i = 0; i < PIXEL_NUM; ++i)
{
for(j = 0; j < 24; ++j)
{
pixelBuffer.All_Buffer[j] = WS_LOW;
}
}
WS281x_Show();
}
uint32_t WS281x_Color(uint8_t red, uint8_t green, uint8_t blue)
{
return green << 16 | red << 8 | blue;
}
void WS281x_SetPixelColor(uint16_t n, uint32_t GRBColor)
{
uint8_t i;
if(n < PIXEL_NUM)
{
for(i = 0; i < GRB; i++)
{
pixelBuffer.All_Buffer[n] = ((GRBColor << i) & 0x800000) ? WS_HIGH : WS_LOW;
}
}
}
void WS281x_SetPixelRGB(uint16_t n ,uint8_t red, uint8_t green, uint8_t blue)
{
uint8_t i;
if(n < PIXEL_NUM)
{
for(i = 0; i < GRB; ++i)
{
pixelBuffer.All_Buffer[n] = (((WS281x_Color(red,green,blue) << i) & 0X800000) ? WS_HIGH : WS_LOW);
}
}
}
// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
uint32_t WS281x_Wheel(uint8_t wheelPos) {
wheelPos = 255 - wheelPos;
if(wheelPos < 85) {
return WS281x_Color(255 - wheelPos * 3, 0, wheelPos * 3);
}
if(wheelPos < 170) {
wheelPos -= 85;
return WS281x_Color(0, wheelPos * 3, 255 - wheelPos * 3);
}
wheelPos -= 170;
return WS281x_Color(wheelPos * 3, 255 - wheelPos * 3, 0);
}
// Fill the dots one after the other with a color
void WS281x_ColorWipe(uint32_t c, uint8_t wait) {
for(uint16_t i=0; i<PIXEL_NUM; i++) {
WS281x_SetPixelColor(i, c);
WS281x_Show();
Delay_ms(wait);
}
}
void WS281x_Rainbow(uint8_t wait) {
uint16_t i, j;
for(j=0; j<256; j++) {
for(i=0; i<PIXEL_NUM; i++) {
WS281x_SetPixelColor(i, WS281x_Wheel((i+j) & 255));
}
WS281x_Show();
Delay_ms(wait);
}
}
// Slightly different, this makes the rainbow equally distributed throughout
void WS281x_RainbowCycle(uint8_t wait) {
uint16_t i, j;
for(j=0; j<256*5; j++) { // 5 cycles of all colors on wheel
for(i=0; i< PIXEL_NUM; i++) {
WS281x_SetPixelColor(i,WS281x_Wheel(((i * 256 / PIXEL_NUM) + j) & 255));
}
WS281x_Show();
Delay_ms(wait);
}
}
//Theatre-style crawling lights.
void WS281x_TheaterChase(uint32_t c, uint8_t wait) {
for (int j=0; j<10; j++) { //do 10 cycles of chasing
for (int q=0; q < 3; q++) {
for (uint16_t i=0; i < PIXEL_NUM; i=i+3) {
WS281x_SetPixelColor(i+q, c); //turn every third pixel on
}
WS281x_Show();
Delay_ms(wait);
for (uint16_t i=0; i < PIXEL_NUM; i=i+3) {
WS281x_SetPixelColor(i+q, 0); //turn every third pixel off
}
}
}
}
//Theatre-style crawling lights with rainbow effect
void WS281x_TheaterChaseRainbow(uint8_t wait) {
for (int j=0; j < 256; j++) { // cycle all 256 colors in the wheel
for (int q=0; q < 3; q++) {
for (uint16_t i=0; i < PIXEL_NUM; i=i+3) {
WS281x_SetPixelColor(i+q, WS281x_Wheel( (i+j) % 255)); //turn every third pixel on
}
WS281x_Show();
Delay_ms(wait);
for (uint16_t i=0; i < PIXEL_NUM; i=i+3) {
WS281x_SetPixelColor(i+q, 0); //turn every third pixel off
}
}
}
}
// Slightly different, this makes the rainbow equally distributed throughout
void WS2812_RainbowRotate(uint16_t wait) {
uint16_t i, j;
for (j = 0; j < 256 * 5; j++) { // 5 cycles of all colors on wheel
for (i = 0; i < PIXEL_NUM; i++) {
WS281x_SetPixelColor(i, WS281x_Wheel(((i * 256 / PIXEL_NUM) + j) & 255));
}
WS281x_Show();
Delay_ms(wait);
}
}
#if SPI_DMA_IRQ
/* ISP for SPI1_TX DMA done. */
void DMA1_Channel2_IRQHandler(void)
{
if (tos_knl_is_running())
{
tos_knl_irq_enter();
if(dma_flag_get(DMA1_FDT2_FLAG) != RESET)
{
dma_channel_enable(DMA1_CHANNEL2, FALSE);
app_dma_xfer_done = true;
dma_flag_clear(DMA1_FDT2_FLAG);
}
tos_knl_irq_leave();
}
}
#endif
void RGB_DEBUG_TEST(void)
{
int i,j;
for(i=0;i<3;i++)
{
for(j=0;j<8;j++)
{
if(i==0)
{
WS281x_ColorWipe(WS281x_Color(255, 0, 255), 1); // Red
}
else if(i==1)
{
WS281x_ColorWipe(WS281x_Color(0, 255, 255), 1); // Green
}
else
{
WS281x_ColorWipe(WS281x_Color(255, 255, 0), 1); // Blue
}
}
}
for(i=0;i<3;i++)
{
for(j=0;j<8;j++)
{
if(i==0)
{
WS281x_ColorWipe(WS281x_Color(255, 0, 0), 10); // Red
}
else if(i==1)
{
WS281x_ColorWipe(WS281x_Color(0, 255, 0), 10); // Green
}
else
{
WS281x_ColorWipe(WS281x_Color(0, 0, 255), 10); // Blue
}
}
}
for(i=0;i<3;i++)
{
for(j=0;j<8;j++)
{
if(i==0)
{
WS281x_ColorWipe(WS281x_Color(255, 0, 0), 10); // Red
Delay_ms(100);
WS281x_ColorWipe(WS281x_Color(0, 255, 0), 10); // Green
}
else if(i==1)
{
WS281x_ColorWipe(WS281x_Color(0, 255, 0), 10); // Green
Delay_ms(100);
WS281x_ColorWipe(WS281x_Color(255, 0, 0), 10); // Red
}
else
{
WS281x_ColorWipe(WS281x_Color(0, 0, 255), 10); // Blue
Delay_ms(100);
WS281x_ColorWipe(WS281x_Color(0, 255, 0), 10); // Green
Delay_ms(100);
WS281x_ColorWipe(WS281x_Color(0, 0, 255), 10); // Blue
Delay_ms(100);
WS281x_ColorWipe(WS281x_Color(255, 0, 0), 10); // Red
}
}
}
WS2812_RainbowRotate(2);
// Some example procedures showing how to display to the pixels:
WS281x_ColorWipe(WS281x_Color(255, 0, 0), 10); // Red
WS281x_ColorWipe(WS281x_Color(0, 255, 0), 10); // Green
WS281x_ColorWipe(WS281x_Color(0, 0, 255), 10); // Blue
WS281x_TheaterChase(WS281x_Color(127, 127, 127), 10); // White
WS281x_TheaterChase(WS281x_Color(127, 0, 0), 10); // Red
WS281x_TheaterChase(WS281x_Color(0, 0, 127), 10); // Blue
WS281x_Rainbow(10);
WS281x_RainbowCycle(10);
WS281x_TheaterChaseRainbow(10);
}3.main.c代码展示
#include "at32f435_437_board.h"
#include "at32f435_437_clock.h"
#include <spi_dma_ws2812.h>
#include "tos_k.h"
#define DELAY 100
#define FAST 1
#define SLOW 4
uint8_t g_speed = FAST;
#define APPLICATION_TASK_STK_SIZE 1024
k_task_t application_task;
uint8_t application_task_stk[APPLICATION_TASK_STK_SIZE];
extern void application_entry(void *arg);
__weak void application_entry(void *arg)
{
WS281x_Init();
while (1)
{
at32_led_toggle(LED2);
tos_task_delay(g_speed * DELAY);
at32_led_toggle(LED3);
tos_task_delay(g_speed * DELAY);
at32_led_toggle(LED4);
tos_task_delay(g_speed * DELAY);
RGB_DEBUG_TEST();
printf("LED_Toggle DEMO!!!\r\n");
}
}
void button_exint_init(void);
void button_isr(void);
/**
* @brief configure button exint
* @param none
* @retval none
*/
void button_exint_init(void)
{
exint_init_type exint_init_struct;
crm_periph_clock_enable(CRM_SCFG_PERIPH_CLOCK, TRUE);
scfg_exint_line_config(SCFG_PORT_SOURCE_GPIOA, SCFG_PINS_SOURCE0);
exint_default_para_init(&exint_init_struct);
exint_init_struct.line_enable = TRUE;
exint_init_struct.line_mode = EXINT_LINE_INTERRUPUT;
exint_init_struct.line_select = EXINT_LINE_0;
exint_init_struct.line_polarity = EXINT_TRIGGER_RISING_EDGE;
exint_init(&exint_init_struct);
nvic_priority_group_config(NVIC_PRIORITY_GROUP_4);
nvic_irq_enable(EXINT0_IRQn, 0, 0);
}
/**
* @brief button handler function
* @param none
* @retval none
*/
void button_isr(void)
{
/* delay 5ms */
tos_task_delay(5);
/* clear interrupt pending bit */
exint_flag_clear(EXINT_LINE_0);
/* check input pin state */
if(SET == gpio_input_data_bit_read(USER_BUTTON_PORT, USER_BUTTON_PIN))
{
if(g_speed == SLOW)
g_speed = FAST;
else
g_speed = SLOW;
}
}
/**
* @brief exint0 interrupt handler
* @param none
* @retval none
*/
void EXINT0_IRQHandler(void)
{
if (tos_knl_is_running())
{
tos_knl_irq_enter();
button_isr();
tos_knl_irq_leave();
}
}
crm_clocks_freq_type crm_clocks_freq_struct = {0};
/**
* @brief main function.
* @param none
* @retval none
*/
int main(void)
{
nvic_priority_group_config(NVIC_PRIORITY_GROUP_4);
system_clock_config();
at32_board_init();
button_exint_init();
/* get system clock */
crm_clocks_freq_get(&crm_clocks_freq_struct);
printf("ahb_freq=%d\r\n",crm_clocks_freq_struct.ahb_freq);
printf("apb1_freq=%d\r\n",crm_clocks_freq_struct.apb1_freq);
printf("apb2_freq=[url=]Removeformat[/url]%d\r\n",crm_clocks_freq_struct.apb2_freq);
printf("sclk_freq=%d\r\n",crm_clocks_freq_struct.sclk_freq);
printf("Welcome to TencentOS tiny(%s)\r\n", TOS_VERSION);
tos_knl_init(); // TencentOS Tiny kernel initialize
tos_task_create(&application_task, "application_task", application_entry, NULL, 0, application_task_stk, APPLICATION_TASK_STK_SIZE, 0);
tos_knl_start();
while(1)
{
}
}
七、效果展示
视频效果图已经发布在了Bilibili,如想了解请移步:【AT32F437的SPI+DMA驱动WS2812B.-哔哩哔哩】 https://b23.tv/YLAgjLK
相关代码工程已经放在了附件里,有兴趣的朋友请下载烧录试试吧,最后有的朋友会问为啥不用TIM+DMA的方式实现呢,我试过,奈何怎么的调不出来,关于TIM+DMA的代码我一并放在附件里,有兴趣的伙伴我们一起来完善它。假期马上结束了,又要投入到紧张的工作中,祝大家新的一年,万事如意,好运连连。
|
|