按這個(gè)步驟 STM32即可完美控制 NeoPixels
問:玩轉(zhuǎn)STM32 - 使用 STM32 來控制 NeoPixels
目前,諸如 Arduino 和 Feather 等高級(jí)開發(fā)平臺(tái)已經(jīng)提供了出色的支持,可以通過易于使用的庫和普遍使用的示例代碼與NeoPixel LED 、燈帶、矩陣 等相連接。然而,更高級(jí)的平臺(tái)(例如 STM32 開發(fā)板 )通常缺乏相同水平的支持。因此,希望將NeoPixels整合到項(xiàng)目中的 開發(fā)人員需要全面了解NeoPixel通信協(xié)議以及如何克服它所帶來的挑戰(zhàn)。
NeoPixels
Adafruit 推出的極受歡迎的可尋址全彩LED燈“NeoPixels”系列分為RGB和RGBW兩個(gè)種類。盡管二者都將紅、綠和藍(lán)色LED與驅(qū)動(dòng)器芯片相集成,但RGBW組件還集成了第四個(gè)純白色的LED??梢允褂妙愃频膯尉€串行接口來控制這兩種類型的NeoPixel,其時(shí)間值和數(shù)據(jù)結(jié)構(gòu)僅存在微小的差異。
WS2812
RGB NeoPixels實(shí)際上是WS2812智能控制LED,包括數(shù)據(jù)信號(hào)輸入引腳(DIN)和數(shù)據(jù)信號(hào)輸出引腳(DOUT)。這允許多個(gè)LED級(jí)聯(lián)并且只用一個(gè)數(shù)據(jù)線進(jìn)行控制。鏈中的第一個(gè)LED負(fù)責(zé)處理從MCU接收到的前三個(gè)字節(jié)數(shù)據(jù),然后將后續(xù)的數(shù)據(jù)簡單地轉(zhuǎn)發(fā)給DOUT引腳,該引腳可以連接到另一個(gè)LED的DIN引腳。LED將以此方式繼續(xù)向下傳遞數(shù)據(jù),直到它們接收到復(fù)位信號(hào)為止(即,DIN線在一段時(shí)間內(nèi)持續(xù)保持低電平狀態(tài))。傳輸?shù)淖止?jié)按照?qǐng)D1所示的協(xié)議進(jìn)行組織。第一個(gè)字節(jié)(G7-G0)表示綠色LED的8位PWM強(qiáng)度,其中0x00是完全關(guān)閉,0xFF是完全打開。類似地,第二個(gè)字節(jié)(R7-R0)用于控制紅色LED的強(qiáng)度,第三個(gè)字節(jié)(B7-B0)用于控制藍(lán)色LED的強(qiáng)度。
圖 1 : WS2812 LED的3字節(jié)數(shù)據(jù)協(xié)議的結(jié)構(gòu)
這些24位數(shù)據(jù)都是通過改變方波的脈沖寬度來進(jìn)行編碼的,如圖2所示。請(qǐng)注意,無論發(fā)送代碼0還是代碼1,方波的周期仍保持在1.25μs。對(duì)于WS2812,使數(shù)據(jù)線保持低電平至少50μs即可生成復(fù)位信號(hào)。另請(qǐng)注意,圖2中顯示的計(jì)時(shí)值具有±0.15μs的公差。
圖2:WS2812 LED的0和1位的計(jì)時(shí)圖
一種截然不同的組件,NeoPixels的RGBW種類實(shí)際上是SK6812智能控制LED,采用與WS2812 LED相同的運(yùn)作原理。然而,由于它們包含第四個(gè)LED,因此實(shí)施了圖3所示的4字節(jié)數(shù)據(jù)協(xié)議。與圖1相比,唯一的區(qū)別在于數(shù)據(jù)的串聯(lián)字節(jié)(W7-W0),該字節(jié)指定了白色LED的8位PWM強(qiáng)度。
圖 3 : SK6812 LED的4字節(jié)數(shù)據(jù)協(xié)議的結(jié)構(gòu)。
圖4展示了SK6812控制信號(hào)的時(shí)間值,同樣與WS2812略有差別(不過仍在±0.15μs的公差范圍內(nèi))。請(qǐng)注意,這兩種代碼的方波周期均保持不變,都為1.2μs。此外,SK6812的復(fù)位信號(hào)長度為80μs ,而非50μs。
圖4:SK6812 LED的0位和1位的計(jì)時(shí)圖。
步驟
由于NeoPixel的控制信號(hào)對(duì)計(jì)時(shí)要求非常嚴(yán)格,因此除非使用匯編語言,否則無法通過簡單的比特帶寬方法產(chǎn)生此信號(hào)。雖然還有許多其他方法可以利用各種MCU外設(shè)、外部硬件或其組合來生成該信號(hào),但其中最直接的方法是配置MCU定時(shí)器來生成PWM輸出信號(hào)。這是因?yàn)?,如上一部分中所述,NeoPixel控制信號(hào)只是一種固定頻率的PWM信號(hào),采用不同的占空比表示0位和1位。為了以與傳輸協(xié)議相同的速率高效地在這兩個(gè)占空比之間進(jìn)行切換,還必須配置DMA流來管理更新。盡管這種方法可能是內(nèi)存效率最低的方式,但它易于理解、CPU高效并且易于實(shí)施(得益于STM32Cube環(huán)境)。
以下應(yīng)用程式利用STM32CubeIDE(版本1.8.0)、NUCLEO-F401RE開發(fā)板和RGBW 5x8 NeoPixel Shield實(shí)現(xiàn)上述的方法。不過,這些步驟可以輕松地推廣到任何STM32 MCU/板和NeoPixel產(chǎn)品上。假定我們已經(jīng)創(chuàng)建了一個(gè)STM32CubeIDE項(xiàng)目。如需使用其他IDE,你可以改為使用獨(dú)立的STM32CubeMX代碼配置器工具,將項(xiàng)目導(dǎo)出到所需的開發(fā)平臺(tái)上。
1.配置PWM
a. 先打開STM32CubeMX配置.ioc 文件(如果還未打開的話)。隨后,STM32CubeIDE將切換到*器件配置工具(*Device Configuration Tool ) 視圖,供你配置MCU。
b. 將定時(shí)器通道備用功能分配給選定的GPIO引腳,以與NeoPixel進(jìn)行連接。所選定時(shí)器通道應(yīng)該能夠生成PWM輸出。圖5顯示了我的項(xiàng)目中的相關(guān)部分,我選擇了引腳PB10,并將它分配給定時(shí)器2、通道3(TIM2_CH3)功能。
圖5:將連接到DIN的GPIO引腳配置為定時(shí)器通道
c. 從左側(cè)的組件列表中選擇上一步中確定的定時(shí)器外設(shè),以打開模式和配置(*Mode and Configuration ) 面板。在模式(*Mode ) 面板中,選擇“內(nèi)部時(shí)鐘”作為時(shí)鐘源,并從適當(dāng)?shù)亩〞r(shí)器通道的下拉列表中選擇“PWM生成CHx”。在圖6中,定時(shí)器2、通道3已設(shè)為“PWM生成CH3”模式,因?yàn)槲以谏弦徊街羞x擇了TIM2_CH3備用功能。請(qǐng)注意,在完成此步驟后,關(guān)聯(lián)的GPIO引腳應(yīng)在引腳排列視圖中從黃色變?yōu)榫G色。
d. 在定時(shí)器的*配置(*Configuration ) 面板中,驗(yàn)證“預(yù)分頻器”和“脈沖”值是否都設(shè)置為0。計(jì)數(shù)器周期,即自動(dòng)重載寄存器(ARR),需要進(jìn)行設(shè)置以得到所需的PWM周期(如果使用RGB WS2812 LED,則為1.25μs;如果使用RGBW SK6812 LED,則為1.2μs)。這將取決于定時(shí)器外設(shè)輸入的速率。只需將所需的PWM周期除以時(shí)鐘周期,并減去1即可得到此值(減去1是因?yàn)槎〝?shù)器從0開始)。就我的器件而言,該公式得出的ARR值為99.8,我將其四舍五入為100(圖6)。請(qǐng)參見下文,了解有關(guān)計(jì)算理想ARR值的詳細(xì)說明。
圖6:將所選定時(shí)器通道配置為PWM輸出
計(jì)算ARR值
假設(shè)定時(shí)器“預(yù)分頻器”值設(shè)為0,可以很容易的計(jì)算出ARR值
具體來說,ARR值等于PWM信號(hào)周期除以定時(shí)器外設(shè)的時(shí)鐘信號(hào)周期。我們知道,根據(jù)使用的NeoPixel類型不同,TPWM可以是1.25μs或1.2μs(例如本例中,TPWM=1.2μs)。要確定Ttimer,你需要查閱器件的規(guī)格書,確定定時(shí)器外設(shè)連接到哪個(gè)總線。規(guī)格書可以在ST的網(wǎng)站上找到或STM32CubeIDE會(huì)隨附提供:選擇幫助>目標(biāo)器件文檔和資源( Help > Target Device Docs and Resources ) 。然后,在MCU 選項(xiàng)卡下選擇規(guī)格書,如圖7所示。
圖 7 : 查找器件規(guī)格書
在我使用的MCU(STM32F401RE)規(guī)格書中,器件框圖中顯示我的定時(shí)器(TIM2)已連接到APB1(見圖8)。
圖 8 : STM32F401xD/xE的部分框圖(源自DS10086)
圖9介紹了:通過切換到STM32CubeIDE中的*時(shí)鐘配置(*Clock Configuration)選項(xiàng)卡,我們可以發(fā)現(xiàn)TIM2的時(shí)鐘頻率為84MHz
圖 9 : 確定定時(shí)器時(shí)鐘頻率
因此,
為了使PWM周期盡可能接近NeoPixel控制信號(hào)的周期,我們四舍五入至最接近的整數(shù)并得到 ARR=100 。
2.配置DMA
a. 從組件列表中選擇DMA外設(shè)。
b. 在配置(Configuration) 面板的DMA1 選項(xiàng)卡下,點(diǎn)擊添加 ( Add ) 按鈕。在下拉菜單中,選擇你的定時(shí)器/通道組合。在我的項(xiàng)目中,我選擇了“TIM2_CH3/UP”。
c. 針對(duì)該新的DMA請(qǐng)求,將方向改為“內(nèi)存到外設(shè)”。
d. 同時(shí),將優(yōu)先級(jí)改為“非常高”。
e. 驗(yàn)證默認(rèn)的DMA請(qǐng)求設(shè)置是否與圖10中顯示的相匹配。
f. 保存.ioc 文件,以生成項(xiàng)目代碼。
圖 10 : 配置DMA流,以便有效更新PWM信號(hào)的占空比
3.編寫代碼
在main.c 文件中,按從上到下的順序編寫,本部分展示了一個(gè)簡單的示例應(yīng)用,用于測試NeoPixel LED的全彩能力。此處提供了兩個(gè)版本的main() 函數(shù),一個(gè)用于RGB WS2818 LED,另一個(gè)用于RGBW SK6812 LED。
a. 在main.c 文件的私有typedef部分,你可以創(chuàng)建一個(gè)新的數(shù)據(jù)類型,以便輕松訪問單個(gè)LED顏色值以及整個(gè)NeoPixel數(shù)據(jù)結(jié)構(gòu)(如圖1和圖3所示)。列表1提供了RGB和RGBW NeoPixel組件的typedef。此代碼應(yīng)粘貼在/* USER CODE BEGIN PTD */ 和/* USER CODE END PTD */ 注釋之間。
列表 1 : 為RGB WS2812和RGBW SK6812 LED自定義數(shù)據(jù)類型
typedef union
{
struct
{
uint8_t b;
uint8_t r;
uint8_t g;
} color;
uint32_t data;
} PixelRGB_t;
typedef union
{
struct
{
uint8_t w;
uint8_t b;
uint8_t r;
uint8_t g;
} color;
uint32_t data;
} PixelRGBW_t;
b. 更改“脈沖”寄存器(也稱為CCRx)的值,這樣可以改變PWM波形的占空比。因此,我們必須計(jì)算適當(dāng)?shù)腃CRx值,以實(shí)現(xiàn)使用的NeoPixels所需的代碼0和代碼1方波(無論是在圖2還是圖4中所示的那些)。對(duì)于RGB WS2812 LED,這些值計(jì)算如下:
ZERO=(ARR+1)(0.32)
ONE=(ARR+1)(0.64)
對(duì)于RGBW SK6812 LED,其計(jì)算過程稍有不同。
ZERO=(ARR+1)(0.25)
ONE=(ARR+1)(0.5)
當(dāng)然,這些計(jì)算出的值應(yīng)該四舍五入到最接近的整數(shù)。在 main.c 文件的私有定義部分,為每個(gè)值創(chuàng)建一個(gè)#define指令(請(qǐng)參見以下圖11中的示例)。
c. 除了CCRx值之外,還應(yīng)在私有定義部分中定義控制的NeoPixel LED數(shù)量和DMA緩沖區(qū)大小。如圖11所示,只需將LED的數(shù)量乘以相應(yīng)的NeoPixel數(shù)據(jù)結(jié)構(gòu)中的位數(shù)即可(回想圖1和圖3)。還必須分配一個(gè)額外的緩沖區(qū)元素,因?yàn)樽詈笠粋€(gè)CCRx值應(yīng)為零(復(fù)位信號(hào))。
圖 11 : WS2812和SK6812 LED的私有定義
d. 將列表2中提供的DMA完成回調(diào)函數(shù)添加到/* USER CODE BEGIN 0 /和/ USER CODE END 0*/之間的私有用戶代碼部分。務(wù)必將 TIM_CHANNEL_x 更改為步驟1c中配置的通道。
列表 2 : HAL_TIM_PWM_PulseFinishedCallback() 函數(shù)的實(shí)施
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
{
HAL_TIM_PWM_Stop_DMA(htim, TIM_CHANNEL_x);
}
e. 最后,必須將應(yīng)用代碼添加到main() 函數(shù)中。列表3提供了一個(gè)使用WS2812 LED的示例main() 函數(shù),而列表4提供了使用SK6812 LED的類似示例main() 函數(shù)。請(qǐng)注意,HAL_TIM_PWM_Start_DMA() 函數(shù)的TIM_CHANNEL_x 參數(shù)必須再次進(jìn)行修改,以匹配步驟1c中配置的通道。
列表 3 : RGB WS2812 LED的示例main() 函數(shù)
int main(void)
{
/* USER CODE BEGIN 1 */
PixelRGB_t pixel[NUM_PIXELS] = {0};
uint32_t dmaBuffer[DMA_BUFF_SIZE] = {0};
uint32_t *pBuff;
int i, j, k;
uint16_t stepSize;
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART2_UART_Init();
MX_DMA_Init();
MX_TIM2_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
k = 0;
stepSize = 4;
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
for (i = (NUM_PIXELS - 1); i > 0; i--)
{
pixel[i].data = pixel[i-1].data;
}
if (k < 255)
{
pixel[0].color.g = 254 - k; //[254, 0]
pixel[0].color.r = k + 1; //[1, 255]
pixel[0].color.b = 0;
}
else if (k < 510)
{
pixel[0].color.g = 0;
pixel[0].color.r = 509 - k; //[254, 0]
pixel[0].color.b = k - 254; //[1, 255]
j++;
}
else if (k < 765)
{
pixel[0].color.g = k - 509; //[1, 255];
pixel[0].color.r = 0;
pixel[0].color.b = 764 - k; //[254, 0]
}
k = (k + stepSize) % 765;
// not so bright
pixel[0].color.g >>= 2;
pixel[0].color.r >>= 2;
pixel[0].color.b >>= 2;
pBuff = dmaBuffer;
for (i = 0; i < NUM_PIXELS; i++)
{
for (j = 23; j >= 0; j--)
{
if ((pixel[i].data >> j) & 0x01)
{
*pBuff = NEOPIXEL_ONE;
}
else
{
*pBuff = NEOPIXEL_ZERO;
}
pBuff++;
}
}
dmaBuffer[DMA_BUFF_SIZE - 1] = 0; // last element must be 0!
HAL_TIM_PWM_Start_DMA(&htim2, TIM_CHANNEL_x, dmaBuffer, DMA_BUFF_SIZE);
HAL_Delay(10);
}
/* USER CODE END 3 */
}
列表 4 : RGBW SK6812 LED的示例main() 函數(shù)
int main(void)
{
/* USER CODE BEGIN 1 */
PixelRGBW_t pixel[NUM_PIXELS] = {0};
uint32_t dmaBuffer[DMA_BUFF_SIZE] = {0};
uint32_t *pBuff;
int i, j, k;
uint16_t stepSize;
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART2_UART_Init();
MX_DMA_Init();
MX_TIM2_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
k = 0;
stepSize = 4;
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
for (i = (NUM_PIXELS - 1); i > 0; i--)
{
pixel[i].data = pixel[i-1].data;
}
if (k < 255)
{
pixel[0].color.g = 254 - k; //[254, 0]
pixel[0].color.r = k + 1; //[1, 255]
pixel[0].color.b = 0;
pixel[0].color.w = 0;
}
else if (k < 510)
{
pixel[0].color.g = 0;
pixel[0].color.r = 509 - k; //[254, 0]
pixel[0].color.b = k - 254; //[1, 255]
pixel[0].color.w = 0;
j++;
}
else if (k < 765)
{
pixel[0].color.g = 0;
pixel[0].color.r = 0;
pixel[0].color.b = 764 - k; //[254, 0]
pixel[0].color.w = k - 509; //[1, 255]
}
else if (k < 1020)
{
pixel[0].color.g = k - 764; //[1, 255]
pixel[0].color.r = 0;
pixel[0].color.b = 0;
pixel[0].color.w = 1019 - k; //[254, 0]
}
k = (k + stepSize) % 1020;
// 50% brightness
pixel[0].color.g >>= 2;
pixel[0].color.r >>= 2;
pixel[0].color.b >>= 2;
pixel[0].color.w >>= 2;
pBuff = dmaBuffer;
for (i = 0; i < NUM_PIXELS; i++)
{
for (j = 31; j >= 0; j--)
{
if ((pixel[i].data >> j) & 0x01)
{
*pBuff = NEOPIXEL_ONE;
}
else
{
*pBuff = NEOPIXEL_ZERO;
}
pBuff++;
}
}
dmaBuffer[DMA_BUFF_SIZE - 1] = 0; // last element must be 0!
HAL_TIM_PWM_Start_DMA(&htim2, TIM_CHANNEL_x, dmaBuffer, DMA_BUFF_SIZE);
HAL_Delay(10);
}
/* USER CODE END 3 */
}
該項(xiàng)目現(xiàn)在應(yīng)該能夠成功構(gòu)建,并支持你在器件上運(yùn)行代碼了。
結(jié)論
使用邏輯分析儀捕獲了上面提供的RGB和RGBW配置生成的控制信號(hào)。分別如圖12和圖13中所示。請(qǐng)注意,它們與圖2和圖4中指定的預(yù)期輸出相匹配。
圖 12 : 生成的WS2812控制信號(hào)(正在發(fā)送0b0011……)
圖 13 : 生成的SK6812控制信號(hào)(正在發(fā)送0b0010……)
*博客內(nèi)容為網(wǎng)友個(gè)人發(fā)布,僅代表博主個(gè)人觀點(diǎn),如有侵權(quán)請(qǐng)聯(lián)系工作人員刪除。