2022年也過去了3個月。年後因爲種種原因,所以評測的事也停了一段時間,今天趁著有空,就又拿起板子搗鼓起來了。
今天的話分享的也不多,就利用電位器加一個WS2812燈環,做一個可調燈色的燈環。使用到的芯片外設主要有兩塊,一個是模數轉換器(ADC),另一個則是串行外設接口(SPI)。
模塊說明
旋轉電位器用到的是下圖的這種,某寶上購買的,內部實際上是一個可調電阻,模擬輸出電壓0~5V。計劃通過讀取該模塊電壓的模擬量來調節WS2812燈環的燈色。
數據傳輸方法:
數據結構:
數據傳輸時間:
時序波形圖
了解完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
*/
staticvoid 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
*/
staticvoid 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();
}
staticvoid 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
**
******************************************************************************/
staticvoid 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();
}
數據發送
staticvoid RGB_Set_Up(void)
{
u8TxBuffer[u16BufferCnt] = WS2812_HIGH;
u16BufferCnt++;
}
staticvoid 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灯色,剩下的就是一些应用逻辑功能的实现。
|