【GD32L233C-START评测】4、电位器+ws2812做一个简易可调灯色的灯环
[复制链接]
本帖最后由 emmnn 于 2022-3-2 00:02 编辑
前言
不知不觉,春节已经过去一个月了,2022年也过去了两个月。年后因为种种原因,所以评测的事也停了一段时间,今天趁着有空,就又拿起板子捣鼓起来了。
今天的话分享的也不多,就利用电位器加一个WS2812灯环,做一个可调灯色的灯环。使用到的芯片外设主要有两块,一个是模数转换器(ADC),另一个则是串行外设接口(SPI)。
模块说明
旋转电位器用到的是下图的这种,某宝上购买的,内部实际上是一个可调电阻,模拟输出电压0~5V。计划通过读取该模块电压的模拟量来调节WS2812灯环的灯色。
WS2812灯环,同样也是在某宝上淘来的,由8个WS2812灯珠串联起来的灯环(如下图)。关于WS2812的介绍,简单的说,这是一种可通过编程来实现调节灯色的灯珠,因为它内部集成了控制电路和发光电路。
按照它的时序要求,我们可以通过发送的数据来控制WS2812C的灯色。以下是它的引脚定义,数据结构,时序图等基本资料

数据传输方法:

数据结构:

数据传输时间:

时序波形图

了解完WS2812的基本信息后,接下来就是如何编程控制了。我们可以从上面的时序波形图和波形时序说明图上看到,WS2812C的时序控制要求达到ns级别,如果说使用IO翻转来实现对它的控制,一方面是难以实现(IO翻转的时间不一定有这么快),另一方面是程序的可移植性也会很差,因此,我们这里采用另一种方法,利用芯片自带的SPI+DMA来实现对WS2812的驱动。
SPI驱动原理说明
关于SPI驱动WS2812原理的说明,这里也不详细介绍,网上大把资料,讲得也比较细。简单说,就是通过SPI外设输出一帧数据,通过这帧数据的高低电平(0和1)占比,来模拟上图码型中的0码和1码。举个例子,我通过调节SPI的分频器,来使得SPI输出一帧数据(通常是8位)的时间周期是1us,那么,这个时候,假如我输出的数据是0xF0,那么这一帧数据所对应的码型就是上图中的0码,若是我输出的数据是0xC0,那么这一帧数据所对应的码型就是上图中的1码。另外,之所以选择SPI+DMA的控制方案,则是考虑到如果在频繁调光的情况下,利用DMA可以极大减轻CPU的负担。
编程实现
首先是关于电位器模拟量的读取功能,用到了MCU的ADC外设,引脚的使用如下:
- #define ROTATION_SENSOR_GPIO_RCU (RCU_GPIOB)
-
- #define ROTATION_SENSOR_GPIO_PORT (GPIOB)
- #define ROTATION_SENSOR_GPIO_PIN (GPIO_PIN_0)
-
- #define ROTATION_ADC_CH (ADC_CHANNEL_8)
adc初始化
-
- static void rcu_config(void)
- {
-
- rcu_periph_clock_enable(RCU_ADC);
-
- rcu_adc_clock_config(RCU_ADCCK_APB2_DIV6);
- }
-
-
- static void adc_config(void)
- {
-
- adc_data_alignment_config(ADC_DATAALIGN_RIGHT);
-
- adc_channel_length_config(ADC_REGULAR_CHANNEL, 1U);
-
-
- adc_external_trigger_source_config(ADC_REGULAR_CHANNEL, ADC_EXTTRIG_REGULAR_NONE);
-
- adc_external_trigger_config(ADC_REGULAR_CHANNEL, ENABLE);
-
-
- adc_enable();
- delay_1ms(1U);
-
- adc_calibration_enable();
- }
-
- static void gpio_config(void)
- {
- rcu_periph_clock_enable(ROTATION_SENSOR_GPIO_RCU);
-
- gpio_mode_set(ROTATION_SENSOR_GPIO_PORT, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, ROTATION_SENSOR_GPIO_PIN);
- }
-
- static uint16_t ADC_Get_Channel(uint8_t channel)
- {
-
- adc_regular_channel_config(0U, channel, ADC_SAMPLETIME_7POINT5);
-
- adc_software_trigger_enable(ADC_REGULAR_CHANNEL);
-
-
- while(!adc_flag_get(ADC_FLAG_EOC));
-
- adc_flag_clear(ADC_FLAG_EOC);
-
- return (adc_regular_data_read());
- }
-
- void rotationSensorInit(void)
- {
- gpio_config();
- rcu_config();
- adc_config();
- }
读取ADC模拟量
- adcval = ADC_Get_Channel(ROTATION_ADC_CH);
接下来是SPI+DMA驱动WS2812部分
先是引脚的定义
- #define SPI1_MOSI_RCU (RCU_GPIOC)
-
- #define WS2812_RCU_PERIPH (RCU_SPI1)
- #define WS2812_PERIPH (SPI1)
-
- #define WS2812_MOSI_PORT (GPIOC)
- #define WS2812_MOSI_PIN (GPIO_PIN_12)
-
- #define WS2812_LOW (0xC0)
- #define WS2812_HIGH (0xF0)
-
- static uint8_t u8TxBuffer[24] = {0};
- static uint16_t u16BufferCnt = 0;
SPI+DMA初始化
-
- static void Spi_DmaConfig(void)
- {
- dma_parameter_struct dma_init_struct;
-
- rcu_periph_clock_enable(RCU_DMA);
-
-
- dma_deinit(DMA_CH0);
- dma_struct_para_init(&dma_init_struct);
- dma_init_struct.request = DMA_REQUEST_SPI1_TX;
- dma_init_struct.direction = DMA_MEMORY_TO_PERIPHERAL;
- dma_init_struct.memory_addr = (uint32_t)u8TxBuffer;
- dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
- dma_init_struct.memory_width = DMA_MEMORY_WIDTH_8BIT;
- dma_init_struct.number = 24;
- dma_init_struct.periph_addr = (uint32_t)&SPI_DATA(WS2812_PERIPH);
- dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
- dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_8BIT;
- dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH;
- dma_init(DMA_CH0, &dma_init_struct);
- }
-
- void WS2812C_Init(void)
- {
- spi_parameter_struct spi_init_struct;
-
- rcu_periph_clock_enable(SPI1_MOSI_RCU);
- rcu_periph_clock_enable(WS2812_RCU_PERIPH);
-
-
- gpio_af_set(WS2812_MOSI_PORT, GPIO_AF_5, WS2812_MOSI_PIN);
- gpio_mode_set(WS2812_MOSI_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, WS2812_MOSI_PIN);
- gpio_output_options_set(WS2812_MOSI_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, WS2812_MOSI_PIN);
-
- spi_i2s_deinit(WS2812_PERIPH);
-
-
- spi_init_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX;
- spi_init_struct.device_mode = SPI_MASTER;
- spi_init_struct.frame_size = SPI_FRAMESIZE_8BIT;
- spi_init_struct.clock_polarity_phase = SPI_CK_PL_LOW_PH_2EDGE;
- spi_init_struct.nss = SPI_NSS_SOFT;
- spi_init_struct.prescale = SPI_PSC_4;
- spi_init_struct.endian = SPI_ENDIAN_MSB;
- spi_init(WS2812_PERIPH, &spi_init_struct);
-
-
- spi_fifo_access_size_config(WS2812_PERIPH, SPI_BYTE_ACCESS);
-
- spi_dma_enable(WS2812_PERIPH, SPI_DMA_TRANSMIT);
-
-
- spi_enable(WS2812_PERIPH);
-
- Spi_DmaConfig();
- }
数据发送
- static void RGB_Set_Up(void)
- {
- u8TxBuffer[u16BufferCnt] = WS2812_HIGH;
- u16BufferCnt++;
- }
-
- static void RGB_Set_Down(void)
- {
- u8TxBuffer[u16BufferCnt] = WS2812_LOW;
- u16BufferCnt++;
- }
-
- void WS2812C_SetRGB(uint32_t RGB888)
- {
- int8_t i = 0;
- uint8_t byte = 0;
-
- u16BufferCnt = 0;
-
- for(i = 23; i >= 0; i--)
- {
- byte = ((RGB888>>i)&0x01);
- if(byte == 1)
- {
- RGB_Set_Up();
- }
- else
- {
- RGB_Set_Down();
- }
- }
-
-
- dma_transfer_number_config(DMA_CH0, 24);
- dma_channel_enable(DMA_CH0);
-
- while (RESET == dma_flag_get(DMA_CH0, DMA_FLAG_FTF))
- {
- }
- dma_flag_clear(DMA_CH0, DMA_FLAG_FTF);
- dma_channel_disable(DMA_CH0);
- }
灯环控制接口
- void ws2812c_All_Ctrl(uint32_t RGB888)
- {
- uint8_t i = 0;
- for(i = 0; i < WS2812C_LED_NUM; i++)
- {
- WS2812C_SetRGB(RGB888);
- }
- }
关于模块的驱动部分大致就这样,然后我们在利用板载的WAKEUP KEY按键来转换电位器所控制的RGB灯色,剩下的就是一些应用逻辑功能的实现。最后效果如下:
工程见附件!以上就是今天介绍的全部内容,如有错误,欢迎大家指出!
|