SG90:

https://item.taobao.com/item.htm?id=678830927467&mi_id=0000bfs3fkPCVUszg031x_05EnwHrp6_hy12yq0S6RyIjgk&skuId=5193877811199&spm=a21xtw.29978518.0.0&xxc=shop

WS2812RGB灯:

https://item.taobao.com/item.htm?id=623668535961&mi_id=0000ASQFV4Rm-kGmtUN_w3qVzYFZc2EIM6cJ2y-qQSnBf38&skuId=4812440351520&spm=a21xtw.29978518.0.0&xxc=shop

1、

2、

3、配SPI

4、加一下lcd驱动,RGB灯驱动

 

5、ws2812.h

#ifndef __WS2812_H
#define __WS2812_H

#include "main.h"
#include "spi.h"
#include "dma.h"

// ========= 配置参数 =========
#define PIXEL_NUM       1
#define WS2812_SPI      hspi1
#define WS2812_DMA      hdma_spi1_tx

#define WS_HIGH  0xF8  // 1111 1000 → 高电平时间更长(1码)
#define WS_LOW   0xE0  // 1110 0000 → 高电平时间更短(0码)

// ========= 函数声明 =========
void ws281x_init(void);
void ws281x_show(void);
void ws281x_setPixelRGB(uint16_t n, uint8_t r, uint8_t g, uint8_t b);
void ws281x_closeAll(void);
uint32_t ws281x_color(uint8_t r, uint8_t g, uint8_t b);

// ========= 外部变量声明 ?? 关键! =========
extern DMA_HandleTypeDef hdma_spi1_tx;
extern SPI_HandleTypeDef hspi1;

#endif

ws2812.c

#include "ws2812.h"

// 像素缓冲区:每个LED需要24字节(3颜色×8bit)
uint8_t pixelBuffer[PIXEL_NUM][24];

// ========= 初始化 =========
void ws281x_init(void)
{
    // HAL库已自动初始化SPI1和DMA,只需确保DMA空闲
    if(HAL_DMA_GetState(&hdma_spi1_tx) != HAL_DMA_STATE_READY)
    {
        HAL_DMA_Abort(&hdma_spi1_tx);
    }
    
    ws281x_closeAll();
    HAL_Delay(100);
}

// ========= 关闭所有灯 =========
void ws281x_closeAll(void)
{
    for(uint16_t i = 0; i < PIXEL_NUM; i++)
    {
        for(uint8_t j = 0; j < 24; j++)
        {
            pixelBuffer[i][j] = WS_LOW;
        }
    }
    ws281x_show();
}

// ========= 颜色转换(GRB顺序) =========
uint32_t ws281x_color(uint8_t red, uint8_t green, uint8_t blue)
{
    // WS2812B需要GRB顺序,不是RGB!
    return ((uint32_t)green << 16) | ((uint32_t)red << 8) | (uint32_t)blue;
}

// ========= 设置单个像素颜色 =========
void ws281x_setPixelRGB(uint16_t n, uint8_t red, uint8_t green, uint8_t blue)
{
    if(n >= PIXEL_NUM) return;
    
    uint32_t grb_color = ws281x_color(red, green, blue);  // GRB顺序
    
    // 编码24位数据到pixelBuffer
    for(uint8_t bit = 0; bit < 24; bit++)
    {
        // 从最高位(MSB)开始判断
        if(grb_color & (0x800000 >> bit))
            pixelBuffer[n][bit] = WS_HIGH;  // 1码
        else
            pixelBuffer[n][bit] = WS_LOW;   // 0码
    }
}

// ========= 发送数据到WS2812B(核心!) =========
void ws281x_show(void)
{
    // 1. 确保DMA空闲
    if(HAL_DMA_GetState(&hdma_spi1_tx) != HAL_DMA_STATE_READY)
    {
        HAL_DMA_Abort(&hdma_spi1_tx);
    }
    
    // 2. 启动SPI+DMA传输
    HAL_SPI_Transmit_DMA(&hspi1, 
                         (uint8_t*)pixelBuffer, 
                         PIXEL_NUM * 24);
    
    // 3. ?? 关键:等待传输完成
    while(HAL_SPI_GetState(&hspi1) != HAL_SPI_STATE_READY)
    {
        __NOP();
    }
    
    // 4. 确保50μs复位时间
    HAL_Delay(3);  // 1ms > 50μs
}
void ws281x_setPixelColor(uint16_t n, uint32_t GRBcolor)
{
    // 提取GRB颜色分量
    uint8_t green = (GRBcolor >> 16) & 0xFF;
    uint8_t red   = (GRBcolor >> 8)  & 0xFF;
    uint8_t blue  = GRBcolor & 0xFF;
    
    // 调用新函数
    ws281x_setPixelRGB(n, red, green, blue);
}

// ========= 可选:流水灯特效(教学演示用) =========
void ws281x_colorWipe(uint32_t c, uint8_t wait)
{
    for(uint16_t i = 0; i < PIXEL_NUM; i++)
    {
        ws281x_setPixelColor(i, c);
        ws281x_show();
        HAL_Delay(wait);  // ← 替换 delay_ms
    }
}

6、main.h

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.h
  * @brief          : Header for main.c file.
  *                   This file contains the common defines of the application.
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2026 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __MAIN_H
#define __MAIN_H

#ifdef __cplusplus
extern "C" {
#endif

/* Includes ------------------------------------------------------------------*/
#include "stm32f1xx_hal.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Exported types ------------------------------------------------------------*/
/* USER CODE BEGIN ET */

/* USER CODE END ET */

/* Exported constants --------------------------------------------------------*/
/* USER CODE BEGIN EC */

/* USER CODE END EC */

/* Exported macro ------------------------------------------------------------*/
/* USER CODE BEGIN EM */

/* USER CODE END EM */

/* Exported functions prototypes ---------------------------------------------*/
void Error_Handler(void);

/* USER CODE BEGIN EFP */

/* USER CODE END EFP */

/* Private defines -----------------------------------------------------------*/

/* USER CODE BEGIN Private defines */
// 4��4
#define KEY_COL1_PIN    GPIO_PIN_0
#define KEY_COL2_PIN    GPIO_PIN_1
#define KEY_COL3_PIN    GPIO_PIN_7
#define KEY_COL4_PIN    GPIO_PIN_8
#define KEY_ROW1_PIN    GPIO_PIN_3
#define KEY_ROW2_PIN    GPIO_PIN_4
#define KEY_ROW3_PIN    GPIO_PIN_5
#define KEY_ROW4_PIN    GPIO_PIN_6
#define KEY_PORT        GPIOB
 
#define KEY_COUNT       16

/* USER CODE END Private defines */

#ifdef __cplusplus
}
#endif

#endif /* __MAIN_H */

7、main.c

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2026 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "adc.h"
#include "dma.h"
#include "spi.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* ========== 功能开关 ========== */
//#define USE_LCD        // 注释这行就禁用LCD,腾出空间
#define USE_WS2812RGB

#include <stdio.h> 
#include <string.h> 

#ifdef USE_LCD
#include "GUI.h"
#include "Lcd_Driver.h"
#endif

#ifdef USE_WS2812RGB
#include "ws2812.h" 
#endif
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

uint8_t humidity;

// ADC variables
uint16_t adc_value = 0;
float voltage = 0.0f;
float light_percent = 0.0f;

// Servo control variables
uint16_t servo_position = 1500;  // Initial position (90 degrees)
#define SERVO_LEFT 500             // 0 degrees
#define SERVO_CENTER 1500          // 90 degrees  
#define SERVO_RIGHT 2500           // 180 degrees

uint8_t servo_hint_id = 0;

#ifdef USE_WS2812RGB
    // 保留颜色变量
    uint8_t ws2812_r = 0, ws2812_g = 0, ws2812_b = 0;
    uint8_t rgb_mode = 0;
#endif

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* ========== 4x4 Matrix Keypad ========== */

/* Keypad key map (4x4) */
const char keypad44_map[4][4] = {
    {'1', '2', '3', 'A'},
    {'4', '5', '6', 'B'},
    {'7', '8', '9', 'C'},
    {'*', '0', '#', 'D'}
};

/* Initialize 4x4 matrix keypad */
void Keypad44_Init(void)
{
    HAL_GPIO_WritePin(KEY_PORT, 
                      KEY_COL1_PIN|KEY_COL2_PIN|KEY_COL3_PIN|KEY_COL4_PIN, 
                      GPIO_PIN_RESET);
}

/* Scan matrix keypad, return key index (0-15), return 0xFF if no key */
uint8_t Keypad44_Scan(void)
{
    uint8_t row, col;
    uint16_t col_pins[4] = {KEY_COL1_PIN, KEY_COL2_PIN, KEY_COL3_PIN, KEY_COL4_PIN};
    uint16_t row_pins[4] = {KEY_ROW1_PIN, KEY_ROW2_PIN, KEY_ROW3_PIN, KEY_ROW4_PIN};
    
    /* Scan each column */
    for(col = 0; col < 4; col++)
    {
        /* Set all columns high, then set current column low */
        HAL_GPIO_WritePin(KEY_PORT, 
                          KEY_COL1_PIN|KEY_COL2_PIN|KEY_COL3_PIN|KEY_COL4_PIN, 
                          (uint16_t)(0x0F << 0));
        HAL_GPIO_WritePin(KEY_PORT, col_pins[col], GPIO_PIN_RESET);
        
        HAL_Delay(1);  /* Short delay for stability */
        
        /* Read all rows */
        for(row = 0; row < 4; row++)
        {
            if(HAL_GPIO_ReadPin(KEY_PORT, row_pins[row]) == GPIO_PIN_RESET)
            {
                /* Key detected, debounce */
                HAL_Delay(20);
                if(HAL_GPIO_ReadPin(KEY_PORT, row_pins[row]) == GPIO_PIN_RESET)
                {
                    /* Wait for key release */
                    while(HAL_GPIO_ReadPin(KEY_PORT, row_pins[row]) == GPIO_PIN_RESET);
                    
                    return col * 4 + row;  /* Return key index (0-15) - FIXED */
                }
            }
        }
    }
    
    return 0xFF;  /* No key pressed */
}

/* Get keypad character */
char Keypad44_GetChar(void)
{
    uint8_t key = Keypad44_Scan();
    if(key != 0xFF)
    {
        return keypad44_map[key / 4][key % 4];
    }
    return 0;
}

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{

  /* USER CODE BEGIN 1 */

  /* 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_DMA_Init();
  MX_USART1_UART_Init();
  MX_ADC1_Init();
  MX_TIM3_Init();
  MX_SPI1_Init();
  /* USER CODE BEGIN 2 */
	printf("===SG90 Test ===\r\n");
	printf("Starting...\r\n");
	
	// Start PWM for servo control
	HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
	HAL_Delay(100);  // Wait for servo initialization
	
#ifdef USE_WS2812RGB
    printf("WS2812B SPI Init...\r\n");
    ws281x_init();  // ← 调用新驱动
    
    // 测试:初始微亮
    ws281x_setPixelRGB(0, 20, 20, 20);
    printf("WS2812B Ready\r\n");
#endif

	/* Initialize 4x4 matrix keypad */
	Keypad44_Init();

	/* LCD initialization */
#ifdef USE_LCD
  Lcd_Init();
  Lcd_Clear(GRAY0);
#endif

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
	static char last_keypad_char = 0;
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
		
		// Start ADC conversion
		HAL_ADC_Start(&hadc1);

		// Wait for conversion to complete
		HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY);

		// Read ADC value
		adc_value = HAL_ADC_GetValue(&hadc1);

		// Stop ADC
		HAL_ADC_Stop(&hadc1);

		// Calculate voltage and light percentage
		voltage = (float)adc_value * 3.3f / 4095.0f;
		light_percent = ((3.3f - voltage) / 3.3f) * 100.0f;

#ifdef USE_LCD		
		// Display Light Sensor title
		Gui_DrawFont_GBK16(0, 70, RED, GRAY0, (u8*)"Light Sensor");

		// Display Light percentage
		char light_str[20];
		sprintf(light_str, "Light: %.1f%%", light_percent);
		Gui_DrawFont_GBK16(0, 90, GREEN, GRAY0, (u8*)light_str);
#endif
		// Print to serial
		//printf("ADC: %d, Volt: %.2fV, Light: %.1f%%\r\n", adc_value, voltage, light_percent);
		
		
		/* Scan 4x4 matrix keypad */
    char keypad44_char = Keypad44_GetChar();
    if(keypad44_char != 0)
    {
        printf("Keypad44: %c\r\n", keypad44_char);
        
        if(keypad44_char != last_keypad_char)
        {
            last_keypad_char = keypad44_char;
#ifdef USE_LCD
						Lcd_Clear(WHITE);
					  Gui_DrawFont_GBK16(0, 10, BLUE, WHITE, (u8*)"Keyboard Display");
            u8 str[20];
            sprintf((char*)str, "Keypad44: %c", keypad44_char);
            Gui_DrawFont_GBK24(0, 30, RED, WHITE, str);
#endif



#ifdef USE_WS2812RGB
        switch(keypad44_char)
        {
            case 'A':
                rgb_mode = 1;
                ws281x_setPixelRGB(0, 255, 255, 255);  // 白色
                ws281x_show();  // ⚠️ 必须调用这个!
								HAL_Delay(50); 
                printf("RGB: WHITE\r\n");
                break;
            case 'B':
                rgb_mode = 2;
                ws281x_setPixelRGB(0, 0, 255, 255);    // 青色
                ws281x_show();  // ⚠️ 必须调用这个!
								HAL_Delay(50); 
                printf("RGB: CYAN\r\n");
                break;
            case 'C':
                rgb_mode = 3;
                ws281x_setPixelRGB(0, 255, 0, 0);      // 红色
                ws281x_show();  // ⚠️ 必须调用这个!
								HAL_Delay(50); 
                printf("RGB: RED\r\n");
                break;
            case '2':
                rgb_mode = 0;  // 恢复光敏自动
                printf("RGB: AUTO (Light Sensor)\r\n");
                break;
            case '3':
                rgb_mode = 4;
                ws281x_setPixelRGB(0, 0, 0, 0);        // 关闭
                ws281x_show();  // ⚠️ 必须调用这个!
								HAL_Delay(100); 
                printf("RGB: OFF\r\n");
                break;
        }
#endif
								
            // Servo control based on keypad input
            switch(keypad44_char)
            {
                case 'A':
                    // Key 1: Turn left 90 degrees (to 0 degrees)
                    servo_position = SERVO_LEFT;
                    __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, servo_position);
                    printf("Servo: Turn LEFT to 0 degrees\r\n");
										servo_hint_id = 1;  
                    break;
                    
                case 'B':
                    // Key 2: Turn right 90 degrees (to 180 degrees)
                    servo_position = SERVO_RIGHT;
                    __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, servo_position);
                    printf("Servo: Turn RIGHT to 180 degrees\r\n");
										servo_hint_id = 2; 
                    break;
                    
                case 'C':
                    // Key 3: Turn left then back to center
                    printf("Servo: Turn LEFT to 0 degrees\r\n");
                    servo_position = SERVO_LEFT;
                    __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, servo_position);
                    HAL_Delay(1000);  // Wait 1 second
                    
                    printf("Servo: Return to CENTER (90 degrees)\r\n");
										servo_hint_id = 3;  
                    servo_position = SERVO_CENTER;
                    __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, servo_position);
                    break;
                    
                default:
										servo_hint_id = 0; 
                    break;
            }
						       
        }
    }
#ifdef USE_LCD
		switch(servo_hint_id)
		{
            case 1:
                Gui_DrawFont_GBK16(0, 50, RED, WHITE, (u8*)"LEFT");
                break;
            case 2:
                Gui_DrawFont_GBK16(0, 50, RED, WHITE, (u8*)"RIGHT");
                break;
            case 3:
                Gui_DrawFont_GBK16(0, 50, RED, WHITE, (u8*)"Center");
                break;
            default:
                Gui_DrawFont_GBK16(0, 50, GRAY0, WHITE, (u8*)"Press A/B/C...");
                break;
		}
#endif
		
#ifdef USE_WS2812RGB
    // WS2812B 光敏自动模式
    static float last_light_for_led = -1.0f;
    float light_diff = light_percent - last_light_for_led;
    if(light_diff < 0) light_diff = -light_diff;
    
    if(rgb_mode == 0 && light_diff > 2.0f) {
        last_light_for_led = light_percent;
        
        // 直接调用新驱动函数
        if(light_percent < 30.0f) {
            ws281x_setPixelRGB(0, 150, 150, 150);  // 白色
        }
        else if(light_percent < 70.0f) {
            ws281x_setPixelRGB(0, 0, 255, 255);    // 青色
        }
        else {
            ws281x_setPixelRGB(0, 255, 0, 0);      // 红色
        }
        ws281x_show();  // ⚠️ 必须调用这个!
    }
#endif

		HAL_Delay(20);
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
  PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC;
  PeriphClkInit.AdcClockSelection = RCC_ADCPCLK2_DIV8;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */
int fputc(int ch,FILE *f)      
{
	HAL_UART_Transmit(&huart1,(uint8_t *)&ch,1,HAL_MAX_DELAY);
	return ch;
}
int fgetc(FILE *f)            
{
	uint8_t ch;
	HAL_UART_Receive(&huart1,(uint8_t *)&ch,1,HAL_MAX_DELAY);
	return ch;
}
/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

8、WS2812RGB灯,DIN连接PA7,VCC连接5V,GND连GND

光敏传感器VCC连3.3V,GND连GND,A0连PC0

键盘这样连接

SG90棕色线接地,红色3.3V,橙色接PC6

9、实验结果

SG90控制逻辑:

按键A:舵机转到0度(左)
按键B:舵机转到180度(右)
按键C:舵机先转到0度,等待1秒,然后回到90度(中间位置)

RGB小灯控制逻辑:

按键A:白灯
按键B:蓝灯
按键C:红灯

按键3:关灯
按键2:切换到光传感器控制,环境光亮度低于30%白灯,大于30%小于70%蓝灯,高于70%变红灯

 

 

 

 

 

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐