[GD32L233C-START Review] 4. Potentiometer + ws2812 to make a simple light ring with adjustable light color
[Copy link]
This post was last edited by emmnn on 2022-3-2 00:02
Preface
Unconsciously, a month has passed since the Spring Festival, and two months have passed in 2022. Due to various reasons after the New Year, the evaluation was stopped for a while. Today, I took advantage of my free time and picked up the board to tinker with it again.
Today, I won’t share much, but I will use a potentiometer and a WS2812 light ring to make an adjustable light ring. There are two main chip peripherals used, one is the analog-to-digital converter (ADC), and the other is the serial peripheral interface (SPI).
Module Description
The rotary potentiometer used is the one in the picture below, which was purchased on Taobao. It is actually an adjustable resistor inside, with an analog output voltage of 0~5V. The plan is to adjust the color of the WS2812 light ring by reading the analog value of the module voltage.
The WS2812 light ring, which was also bought on Taobao, is a light ring made of 8 WS2812 lamp beads connected in series (as shown below). To put it simply, WS2812 is a lamp bead that can be programmed to adjust the light color because it integrates the control circuit and the light-emitting circuit.
According to its timing requirements, we can control the light color of WS2812C by sending data. The following is its pin definition, data structure, timing diagram and other basic information
Data transfer method:
Data structure:
Data transfer time:
Timing waveform
After understanding the basic information of WS2812, the next step is how to program and control it. We can see from the above timing waveform and waveform timing description that the timing control of WS2812C is required to reach the ns level. If we use IO flip to control it, it is difficult to achieve on the one hand (IO flip time may not be so fast), and on the other hand, the portability of the program will be very poor. Therefore, we use another method here to use the chip's built-in SPI+DMA to drive WS2812.
SPI driver principle description
The explanation of the principle of SPI driving WS2812 is not introduced in detail here. There are a lot of materials on the Internet, and they are also quite detailed. Simply put, a frame of data is output through the SPI peripheral, and the proportion of high and low levels (0 and 1) of this frame of data is used to simulate the 0 code and 1 code in the above code. For example, I adjust the SPI divider to make the time period of SPI outputting a frame of data (usually 8 bits) 1us. Then, at this time, if the data I output is 0xF0, then the code pattern corresponding to this frame of data is the 0 code in the above figure. If the data I output is 0xC0, then the code pattern corresponding to this frame of data is the 1 code in the above figure. In addition, the reason for choosing the SPI+DMA control scheme is that if the light is frequently dimmed, the use of DMA can greatly reduce the burden on the CPU.
Programming Implementation
First, the analog reading function of the potentiometer uses the ADC peripheral of the MCU. The pin usage is as follows:
#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 initialization
/*!
\brief configure the different system clocks
\param[in] none
\param[out] none
\retval none
*/
static void rcu_config(void)
{
/* enable ADC clock */
rcu_periph_clock_enable(RCU_ADC);
/* config ADC clock */
rcu_adc_clock_config(RCU_ADCCK_APB2_DIV6);
}
/*!
\brief configure the ADC peripheral
\param[in] none
\param[out] none
\retval none
*/
static void adc_config(void)
{
/* ADC data alignment config */
adc_data_alignment_config(ADC_DATAALIGN_RIGHT);
/* ADC channel length config */
adc_channel_length_config(ADC_REGULAR_CHANNEL, 1U);
/* ADC trigger config */
adc_external_trigger_source_config(ADC_REGULAR_CHANNEL, ADC_EXTTRIG_REGULAR_NONE);
/* ADC external trigger config */
adc_external_trigger_config(ADC_REGULAR_CHANNEL, ENABLE);
/* enable ADC interface */
adc_enable();
delay_1ms(1U);
/* ADC calibration and reset calibration */
adc_calibration_enable();
}
static void gpio_config(void)
{
rcu_periph_clock_enable(ROTATION_SENSOR_GPIO_RCU);
/* PB0 ADC_IN config */
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 */
adc_regular_channel_config(0U, channel, ADC_SAMPLETIME_7POINT5);
/* ADC software trigger enable */
adc_software_trigger_enable(ADC_REGULAR_CHANNEL);
/* wait the end of conversion flag */
while(!adc_flag_get(ADC_FLAG_EOC));
/* clear the end of conversion flag */
adc_flag_clear(ADC_FLAG_EOC);
/* return regular channel sample value */
return (adc_regular_data_read());
}
void rotationSensorInit(void)
{
gpio_config();
rcu_config();
adc_config();
}
Read ADC analog value
adcval = ADC_Get_Channel(ROTATION_ADC_CH);
Next is the SPI+DMA driver WS2812 part
First, the definition of the pin
#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 initialization
/**
*******************************************************************************
** \brief Configure SPI DMA function
**
** \param [in] None
**
** \retval None
**
******************************************************************************/
static void Spi_DmaConfig(void)
{
dma_parameter_struct dma_init_struct;
/* enable DMA clock */
rcu_periph_clock_enable(RCU_DMA);
/* initialize DMA channel 0 */
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);
/* SPI1_MOSI(PC12) GPIO pin configuration */
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);
/* SPI1 parameter config */
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; //CS脚由软件托管
spi_init_struct.prescale = SPI_PSC_4;
spi_init_struct.endian = SPI_ENDIAN_MSB;
spi_init(WS2812_PERIPH, &spi_init_struct);
/* configure SPI1 byte access to FIFO */
spi_fifo_access_size_config(WS2812_PERIPH, SPI_BYTE_ACCESS);
spi_dma_enable(WS2812_PERIPH, SPI_DMA_TRANSMIT);
/* enable SPI1 */
spi_enable(WS2812_PERIPH);
Spi_DmaConfig();
}
Data transmission
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);
}
Light ring control interface
void ws2812c_All_Ctrl(uint32_t RGB888)
{
uint8_t i = 0;
for(i = 0; i < WS2812C_LED_NUM; i++)
{
WS2812C_SetRGB(RGB888);
}
}
That's about it for the module's driver. Then we use the onboard WAKEUP KEY button to switch the RGB light color controlled by the potentiometer. The rest is the implementation of some application logic functions. The final effect is as follows:
The project is attached! The above is all the content introduced today. If there are any errors, please point them out!
|