2026寒假练 - 用RP2350B核心板完成光效与调光实验
该项目使用了VSCode软件,C语言,实现了一个RP2350B核心板的设计,它的主要功能为:基于 8 颗单色 LED 与 2 颗 WS2812 实现呼吸、频闪、流水、随机闪烁 4 种光效,通过 4 按键切换光效、调节亮度,4 拨码选择速度档位,双位七段数码管显示光效编号与速度档位,并通过 USB 虚拟串口输出参数。。
标签
WS2812
参加活动/培训
RP2350B
知足常乐
更新2026-03-24
41

一、任务介绍

1、用8颗单色 LED 与 2 颗 WS2812 实现至少 4 种光效:呼吸、频闪、流水、随机闪烁。
2、4 个按键用于光效切换、速度加减、亮度加减;4 个拨码开关用于选择速度档位或占空比档位。
3、双位七段数码管显示当前光效编号与速度档位。

4、 USB 虚拟串口输出当前光效参数,便于上位机观察。


二、硬件平台

基于RP2350B设计,采用DIP40封装,支持小脚丫FPGA外设扩展板的大部分功能,板上有10颗单色LED、2颗三色彩灯、4个按键、4个开关、2个数码管以及一个姿态传感器,并有SWD调试端口,采用蛇形排孔,方便在无焊接的情况下进行扩展。

核心板:

  • 8颗单色LED,可以用于各种LED相关的编程练习

image.png

  • 2个三色LED,用于使用RGB三种颜色的编程练习,这里的2颗三色灯是通过一根串行信号线来控制

image.png

  • 2个7段数码管,用于计数、显示数字信息,可以显示0-99之间的数字,在这里使用了两颗74HC595将3根控制信号线转换为7段数码管的驱动信号

image.png

  • 4个拨码开关,用于设置、切换一些状态,其状态是使用模拟信号的方式送到RP2350B的ADC进行判断
  • 4个轻触按键,用于控制信息的输入,其状态是使用模拟信号的方式送到RP2350B的ADC进行判断

image.png


三、设计方案

image.png

图1 硬件系统架构

1、输入:

按键设计:4 个按键分别映射 “光效+、光效-、亮度 +、亮度 -”。

拨码开关:映射 4 档速度(1-4 档)。

2、输出:

单色 LED:通过 PWM 驱动,占空比调节亮度,不同光效对应不同的GPIO控制方式。

WS2812 RGB 灯:通过PIO进行驱动,进而实现颜色渐变、呼吸效果。

双位数码管:采用 74HC595,减少 GPIO 占用,左位显示光效 ID(1-4 对应 4 种光效),右位显示速度档位(1-4)。

3、通信:

配置 RP2350B 的 USB 外设为虚拟串口。


四、代码介绍

使用的是C语言,软件使用VSCode,软件里面可以下载PICO的SDK,直接进行开发,非常方便。

模块划分框图

image.png

图2 底层驱动初始化模块

image.png

图3 输入处理模块

image.png

图4 光效控制模块

image.png

图5 输出模块

软件执行流程框图

image.png

图6 软件总的执行框图

1、用8颗单色 LED 与 2 颗 WS2812实现至少 4 种光效:呼吸、频闪、流水、随机闪烁。

8个LED是连接在4个IO上的,所以要想一个亮,就需要把用到的两个初始化为输出,其他的IO为高阻态,同时,为了实现后面的亮度控制,又把输出高电平的IO设为PWM输出,下面是初始化后,LED3会亮,其他LED灭。

 				gpio_set_dir(IO4,0);
        gpio_set_dir(IO3,0);
        gpio_set_dir(IO2,1);
        gpio_set_dir(IO1,1);
        gpio_set_pulls(IO4,0,1);
        gpio_set_pulls(IO3,0,1);
        gpio_put(IO2,0);
        gpio_set_function(IO1,GPIO_FUNC_PWM);
        slice_num[3] = pwm_gpio_to_slice_num(IO1);
        pwm_set_wrap(slice_num[3], wrap);
        pwm_set_clkdiv(slice_num[3], 1.0f);
        pwm_set_chan_level(slice_num[3], pwm_gpio_to_channel(IO1), duty_liangdu);
        pwm_set_enabled(slice_num[3], true);

换一个 LED点亮的话,需要先把之前的PWM关掉,再把想要点亮LED相关的LED初始化。

        pwm_set_enabled(slice_num[3], false);
        gpio_set_function(IO1, GPIO_FUNC_SIO);
        led_off();

这里的led_off();函数代码如下,是因为我简单只把IO功能改回来还是会有误点亮。

void led_off(void)
{
    gpio_set_dir(IO4,1);
    gpio_set_dir(IO3,1);
    gpio_set_dir(IO2,1);
    gpio_set_dir(IO1,1);
    gpio_put(IO4,0);
    gpio_put(IO3,0);
    gpio_put(IO2,0);
    gpio_put(IO1,0);
}

想要实现呼吸灯的话,就是不断地改变PWM的占空比;频闪可以理解为LED快速地亮灭;流水效果就是按照LED的排列顺序,让LED轮流点亮;随机闪烁效果,我是使用伪随机数,随机点亮选中的LED。

WS2812是一个集控制电路与发光电路于一体的智能外控LED光源。其外型与一个5050LED灯珠相同,每个 元件即为一个像素点。像素点内部包含了智能数字接口数据锁存信号整形放大驱动电路,还包含有高精度的内部 振荡器和可编程定电流控制部分,有效保证了像素点光的颜色高度一致。 数据协议采用单线归零码的通讯方式,像素点在上电复位以后,DIN端接受从控制器传输过来的数据,首先 送过来的24bit数据被第一个像素点提取后,送到像素点内部的数据锁存器,剩余的数据经过内部整形处理电路整 形放大后通过DO端口开始转发输出给下一个级联的像素点,每经过一个像素点的传输,信号减少24bit。像素点 采用自动整形转发技术,使得该像素点的级联个数不受信号传送的限制,仅受限信号传输速度要求。

我在网上找到多种驱动WS2812的方式,有PWM+DMA,有SPI+DMA,还有PIO+DMA。这里驱动使用的是PIO+DMA。PIO代码如下:

.program ws2812

.define public T0H 3
.define public T1H 6
.define public TL 6

.wrap_target
pull block
set x, 23

bitloop:
set pins, 1
out y, 1 [T0H - 3]
jmp !y skip
nop [T1H - T0H - 1]

skip:
set pins, 0 [TL - 2]
jmp x-- bitloop

.wrap

然后就是正常PIO的初始化过程。需要注意的重点有两个,一个是分频,用下面代码:

		float freq = 1.0 / 0.7e-6 * ws2812_TL;
    float clk_div = clock_get_hz(clk_sys) / freq;

另一个是PIO与GPIO绑定的问题,默认情况下单个PIO只能控制32个IO,所使用核心板连接WS2812用的IO是GPIO46,所以默认是绑定不上的,所以必须使用以下函数:

pio_set_gpio_base(pio, 16);

对于RP2350来说,第二个参数可以选择0/16,0对应pins0-31,16对应32-47.必须在的创建实例和绑定函数之间使用这个函数,之后PIO与GPIO46才能正常绑定使用。


2、4 个按键用于光效切换、亮度加减;4 个拨码开关用于选择速度档位。

首先需要判断哪个键动了,硬件电路是R2R网络,根据ADC采样回来的值不一样,可以判断是哪个按键按下了。ADC的采样值需要滤波:

uint16_t adc_num(void)
{
    uint16_t curr_value, max_value, min_value;
    uint32_t sum;
    uint8_t i;
    curr_value = adc_read();
    max_value = curr_value;
    min_value = curr_value;
    sum+=curr_value;
    for(i=0;i<(ADC_AVG_NUM-1);i++)
    {
       curr_value = adc_read();
       if(curr_value > max_value)
       {
           max_value = curr_value;
       }
       if(curr_value < min_value)
       {
           min_value = curr_value;
       }
       sum+=curr_value;
    }
    sum-=min_value;
    sum-=max_value;
    return (sum/(ADC_AVG_NUM-2));    
}

经过多次判断之后才会返回采样值,然后但KEY值判断里面判断是哪个键。

对于按键,我选用两个按键负责光效切换,两个按键负责亮度加减。4个拨码开关对应4个速度挡位。光效实现就用的是前面用的LED点亮函数,只是把它们写在中断中,同时用状态机管理,延时使用进入中断的次数判断。

void key_process_for_led(uint8_t key_value)
{
    if(key_last_value != key_value)
    {
        if((key_value>=SW0) && (key_value<=KEY3))
        {
            printf("key=%x \r\n", key_value);
        }
        key_last_value = key_value;
        switch (key_value)

按一次判断一次,然后用switch 判断进入对应状态。速度挡位切换和亮度加减都在KEY值判断函数中直接运行;光效切换是给一个标志位,然后在中断中亮对应的LED亮灯效果。

case SW0:
             stat_flag1=1;
             waterfall_change_speed(200);
             breathing_led_set_speed_direct(8);
             blink_flex_set_times(50,100);
             simple_random_set_speed_ms(40);
            break;
case KEY3:
            if (duty_liangdu >= 30 + 40)
            {  
                duty_liangdu -= 40;
            } else {
                duty_liangdu = 30;
            }
if (led_num >= 4)
            {
                led_num = 4;
            }
            switch (led_num)
            {
            case 1:stat_flag=5;break;
            case 2:stat_flag=6;break;
            case 3:stat_flag=7;break;
            case 4:stat_flag=8;break;
            default:break;
            }

中断中执行:

 switch (stat_flag)
    {
    case 5:
        sendto595_1(NUM_1);
        if (timer_huxi == 1)
        {
            led_off();
            led10_huxi_init();
            timer_huxi = 0;
        }
        breathing_led_timer_isr();
        break;


3、双位七段数码管显示当前光效编号与速度档位。

重点是74HC595芯片,搞清楚使用方法。74HC595的最重要的功能就是:串行输入,并行输出。其次,74HC595里面有2个8位寄存器:移位寄存器、存储寄存器。74HC595的数据来源只有一个口,一次只能输入一个位,那么连续输入8次,就可以积攒为一个字节了。

  • 74HC595的14脚:SER 英文全称是:Serial data input ,顾名思义,就是串行数据输入口。
  • 74HC595的11脚:SRCLK(shift register clock input) 移位寄存器时钟引脚,上升沿有效。
  • 74HC595的12脚:RCLK (storage register clock input ) 存储寄存器时钟
void sendto595_1(uint8_t num)
{
    uint8_t bit_num;
    uint16_t real_num = ((uint16_t) 0x02 << 8) | (uint16_t)num ;
    for (char i = 0; i < 16; i++)
    {
        bit_num = (real_num >> 15) & 0x01;
        gpio_put(SER,bit_num);
        real_num = real_num << 1;


        gpio_put(SCK,1);
        sleep_us(1);
        gpio_put(SCK,0);
    }
    gpio_put(RCK,1);
    sleep_us(1);
    gpio_put(RCK,0);
   
}

把第4行的0x02改为0x01就是写另一个数码管。

数码管的信息如图所示,想要什么样的数字,写对应的位就可以了。

image.png

写速度相关数码管是在低级中断里,KEY值判断之后给一个状态标志位,然后在中断里执行写数码管。光效相关的数码管在切换光效时就随着标志位变化执行了。

bool low_priority_timer_callback(repeating_timer_t *rt) 
{
    switch (stat_flag1)
    {
    case 1:
        sendto595_2(NUM_1);
        break;
    case 2:
        sendto595_2(NUM_2);
        break;
    case 3:
        sendto595_2(NUM_3);
        break;
    case 4:
        sendto595_2(NUM_4);
        break;
    default:
        break;
    }


4、 USB 虚拟串口输出当前光效参数,便于上位机观察。

在创建工程时,选择右边的console over USB,然后在工程中使用printf打印就可以在串口输出了。

image.png

如果在创建时没有选也不要紧,在CMakeLists.txt文件中添加如下代码即可。

# Modify the below lines to enable/disable output over UART/USB
pico_enable_stdio_uart(Z1 0)
pico_enable_stdio_usb(Z1 1)


五、实物展示图

微信图片_20260131181406_297_55.jpgimage.png

图中数据线连接VSCode串口读取打印出来的光效信息,左边的数码管展示第几个光效,右边的数码管展示第几档速度。

四个光效全部由8个LED灯实现,WS2812从上电开始执行彩色变化+呼吸。

拨码开关切换速度挡位,共四个挡位可切换,1最慢,4最快,切换同时数码管也会显示相应挡位,串口输出的速度挡位也随着变化

上面两个按键负责切换光效,共四个光效可切换,一个光效+,一个光效-,伴随光效变化,数码管与串口输出的光效编号也随着变化。

下面两个按键负责亮度变化,共四个亮度可切换,一个亮度+,一个亮度-,可以看到LED的亮度变化,串口输出的亮度挡位也随着变化。


六、项目中遇到的难题及解决办法

1、实现了8个LED光效,不会按键检测与LED闪烁同时运行。解决办法:改造原本的LED函数,全部使用中断运行,主函数按键检测。

2、不会使用74HC595。解决方法:查资料。

3、驱动不了WS2812。解决方法:1、参考例程写PIO代码。2、RP2350B的PIO绑定GPIO46还需要多一步设置。


七、心得体会

之前少有机会写代码,更别说完成一个小成果了。经过这次完成任务的学习,对树莓派的RP2350B芯片有了更多的了解,对PICO SDK的使用更加熟练,学会使用WS2312、74HC595这样的外设,对于LED灯,R2R按键检测电路设计有了更多的了解。对自己写代码的能力也有锻炼,逐渐学习,减少生产石山代码。让自己更脚踏实地,以前总是眼高手低,觉得自己有思路就不去做了,殊不知自己的逻辑和代码编写能力都有很大的漏洞。

感谢硬禾为我们提供了这样的活动,扩展自己的知识面,希望今后活动越办越好。






附件下载
Z1.7z
团队介绍
个人
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号