【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初始化
/*!
\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();
}
读取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初始化
/**
*******************************************************************************
** \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();
}
数据发送
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灯色,剩下的就是一些应用逻辑功能的实现。最后效果如下:
工程见附件!以上就是今天介绍的全部内容,如有错误,欢迎大家指出!
|