Raspberry Pi Pico 2 W Specifications
| Microcontroller | Raspberry Pi RP2350 (dual-core Arm Cortex-M33 + dual-core RISC-V Hazard3) |
|---|---|
| Clock Speed | Up to 150 MHz |
| SRAM | 520 KB on-chip |
| Flash Memory | 4 MB QSPI flash |
| Wireless |
2.4 GHz Wi-Fi (802.11n) Bluetooth 5.2 |
| GPIO Pins | 26 multi-function GPIO |
| Interfaces |
2 Γ UART 2 Γ SPI 2 Γ IΒ²C USB 1.1 (device) 16 Γ PWM channels 3 Γ 12-bit ADC |
| Operating Voltage | 1.8V β 5.5V input (3.3V logic) |
| Dimensions | 51 mm Γ 21 mm |
| Release Year | 2024 |
Note: ChatGPT was used to assist with code development and Raspberry Pi setup.
The Raspberry Pi Pico supports both Python (MicroPython) and C/C++ development. Firmware can be flashed to enable either environment. The board includes 4MB of onboard Flash memory for firmware and user programs.
I have more experience in C/C++, but decided to use Python as a learning experience. Itβs faster to prototype with and easier to iterate on.
Besides⦠all the cool kids are using Python!
With the 4MB Flash memory, there is plenty of space for embedded projects:
- MicroPython firmware: ~600β800 KB
- Filesystem available for user code: ~3 MB
- Simple Python file: typically 1β5 KB
- Medium Python project: typically 10β50 KB
- Enough room to store multiple projects and reusable helper libraries
Thonny is a lightweight Python IDE with built-in support for MicroPython devices such as the Raspberry Pi Pico.
When you click Run, Thonny transfers the script to the Pico over USB and executes it directly in RAM for fast testing and iteration.
If the program is saved as main.py on the Picoβs flash filesystem, it will automatically execute on power-up.
Video showing the Raspberry Pi Pico 2W controlling an LED bar to display flowing lights using Python.
View Python source: main.py
#######
#
# Project: PWM (Pulse Width Modulation) LED Chase Effect
# Based on open-source PWM examples from the Raspberry Pi Foundation
# and MicroPython community.
#
# Adapted and documented by Denis Legault
# Date: 2026-02-10
# Python File: main.py
# Hardware: Raspberry Pi Pico 2 W,
# 10 LEDs connected via PWM-capable pins
#
# Description:
# Demonstrates a PWM-based LED chase effect using a custom myPWM class.
# The LEDs gradually fade in and out using different duty cycle values,
# creating a smooth flowing light pattern from left to right and then
# right to left.
#
# PWM duty values are cycled through a predefined list to control
# brightness levels. The effect runs continuously until interrupted.
#
# GPIO Pin Connections:
# PWM Pins -> GPIO16, 17, 18, 19, 20, 21, 22, 26, 27, 28
#
# Notes:
# - Requires pwm.py containing the myPWM class.
# - Uses MicroPython PWM (16-bit duty cycle: 0β65535).
# - Saved as main.py to auto-run on power-up.
#
#######
from machine import Pin, PWM
from pwm import myPWM
import time
# Initialize PWM controller with 10 GPIO pins
mypwm = myPWM(16, 17, 18, 19, 20, 21, 22, 26, 27, 28)
# Logical PWM channel numbers (0β9)
chns = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# Duty cycle values used to create a fade-in / fade-out effect
# 0 = OFF, 65535 = FULL brightness
dutys = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
65535, 32768, 16384, 8192, 4096,
2048, 1024, 512, 256, 128,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
]
# Delay between animation steps (milliseconds)
delayTimes = 50
try:
while True:
# Forward LED chase
for i in range(0, 20):
for j in range(0, 10):
# Set PWM duty for each channel
mypwm.ledcWrite(chns[j], dutys[i + j])
time.sleep_ms(delayTimes)
# Reverse LED chase
for i in range(0, 20):
for j in range(0, 10):
# Reverse channel order for opposite direction
mypwm.ledcWrite(chns[9 - j], dutys[i + j])
time.sleep_ms(delayTimes)
except:
# Cleanly disable all PWM outputs on exit
mypwm.deinit()
View Python source: pwm.py
#######
#
# Module: PWM Helper Class (myPWM)
# Based on open-source PWM (Pulse Width Modulation) examples from the
# Raspberry Pi Foundation and MicroPython community.
#
# Adapted and documented by Denis Legault
# Date: 2026-02-10
# Python File: pwm.py
# Hardware: Raspberry Pi Pico 2 W
#
# Description:
# Provides a simple wrapper class around MicroPython's PWM functionality
# to control up to 10 PWM output channels using logical channel numbers
# (0β9) instead of managing individual PWM objects directly.
#
# Each PWM channel is initialized with the same frequency and can be
# updated independently using a 16-bit duty cycle value (0β65535).
#
# This module is intended to be imported by other programs (e.g. main.py)
# and is commonly used for LED dimming effects, motor control, or
# multi-channel PWM animations.
#
# GPIO Pin Connections (default):
# PWM Channel 0 -> GPIO16
# PWM Channel 1 -> GPIO17
# PWM Channel 2 -> GPIO18
# PWM Channel 3 -> GPIO19
# PWM Channel 4 -> GPIO20
# PWM Channel 5 -> GPIO21
# PWM Channel 6 -> GPIO22
# PWM Channel 7 -> GPIO26
# PWM Channel 8 -> GPIO27
# PWM Channel 9 -> GPIO28
#
# Notes:
# - Uses MicroPython PWM with 16-bit duty resolution.
# - Frequency defaults to 10 kHz but can be changed during initialization.
# - Designed to simplify multi-channel PWM usage in embedded projects.
#
#######
from machine import Pin, PWM
class myPWM:
"""
Wrapper class for managing multiple PWM outputs using logical channels.
"""
def __init__(
self,
pwm0: int = 16,
pwm1: int = 17,
pwm2: int = 18,
pwm3: int = 19,
pwm4: int = 20,
pwm5: int = 21,
pwm6: int = 22,
pwm7: int = 26,
pwm8: int = 27,
pwm9: int = 28,
freq_num: int = 10000
):
"""
Initialize up to 10 PWM channels with a common frequency.
:param pwm0βpwm9: GPIO pin numbers for each PWM channel
:param freq_num: PWM frequency in Hz (default: 10 kHz)
"""
# Create PWM objects for each channel and set frequency
self._pwm0 = PWM(Pin(pwm0))
self._pwm0.freq(freq_num)
self._pwm1 = PWM(Pin(pwm1))
self._pwm1.freq(freq_num)
self._pwm2 = PWM(Pin(pwm2))
self._pwm2.freq(freq_num)
self._pwm3 = PWM(Pin(pwm3))
self._pwm3.freq(freq_num)
self._pwm4 = PWM(Pin(pwm4))
self._pwm4.freq(freq_num)
self._pwm5 = PWM(Pin(pwm5))
self._pwm5.freq(freq_num)
self._pwm6 = PWM(Pin(pwm6))
self._pwm6.freq(freq_num)
self._pwm7 = PWM(Pin(pwm7))
self._pwm7.freq(freq_num)
self._pwm8 = PWM(Pin(pwm8))
self._pwm8.freq(freq_num)
self._pwm9 = PWM(Pin(pwm9))
self._pwm9.freq(freq_num)
def ledcWrite(self, chn, value):
"""
Set the PWM duty cycle for a given channel.
:param chn: Logical PWM channel number (0β9)
:param value: Duty cycle (0β65535)
"""
if chn == 0:
self._pwm0.duty_u16(value)
elif chn == 1:
self._pwm1.duty_u16(value)
elif chn == 2:
self._pwm2.duty_u16(value)
elif chn == 3:
self._pwm3.duty_u16(value)
elif chn == 4:
self._pwm4.duty_u16(value)
elif chn == 5:
self._pwm5.duty_u16(value)
elif chn == 6:
self._pwm6.duty_u16(value)
elif chn == 7:
self._pwm7.duty_u16(value)
elif chn == 8:
self._pwm8.duty_u16(value)
elif chn == 9:
self._pwm9.duty_u16(value)
def deinit(self):
"""
Disable all PWM channels and release hardware resources.
"""
self._pwm0.deinit()
self._pwm1.deinit()
self._pwm2.deinit()
self._pwm3.deinit()
self._pwm4.deinit()
self._pwm5.deinit()
self._pwm6.deinit()
self._pwm7.deinit()
self._pwm8.deinit()
self._pwm9.deinit()
Video showing the Raspberry Pi Pico 2W controlling of a NeoPixel (WS2812) LED strip using a custom myNeopixel class. The program cycles through a predefined list of colors (red, green, blue, white, and off), displaying each color across all LEDs for 0.5 seconds.
View Python source: main.py
#######
#
# Project: NeoPixel Color Cycle
# Based on open-source WS2812 PIO examples from the Raspberry Pi Foundation
# and MicroPython community.
#
# Adapted and documented by Denis Legault
# Date: 2026-02-10
# Python File: main.py
# Hardware: Raspberry Pi Pico 2 W,
# 8x WS2812 / NeoPixel LEDs connected to GPIO16
#
# Description:
# Demonstrates basic control of a NeoPixel (WS2812) LED strip using a
# custom myNeopixel class. The program cycles through a predefined list
# of colors (red, green, blue, white, and off), displaying each color
# across all LEDs for 0.5 seconds.
#
# The brightness level is set globally before entering the loop.
# The effect runs continuously until interrupted.
#
# GPIO Pin Connections:
# NeoPixel Data In -> GPIO16
# 5V -> External 5V supply (recommended)
# GND -> Common ground with Pico
#
# Notes:
# - Requires neopixel.py containing the myNeopixel class.
# - NeoPixels use RGB color values (0β255 per channel).
# - brightness() scales overall intensity (0β255).
# - Saved as main.py to auto-run on power-up.
#
#######
import time
from machine import Pin
from neopixel import myNeopixel
# Number of LEDs in the strip
NUM_LEDS = 8
# Initialize NeoPixel object
# Parameters: (number_of_leds, GPIO_pin)
np = myNeopixel(NUM_LEDS, 16)
# Define RGB color tuples
red = (255, 0, 0)
green = (0, 255, 0)
blue = (0, 0, 255)
white = (255, 255, 255)
close = (0, 0, 0) # LEDs off
# List of colors to cycle through
COLORS = [red, green, blue, white, close]
# Set global brightness (0β255)
np.brightness(10) # Low brightness to reduce power draw
while True:
# Loop through each predefined color
for color in COLORS:
# Set all LEDs to the current color
np.fill(color[0], color[1], color[2])
# Update the strip to display changes
np.show()
# Wait before changing to the next color
time.sleep(0.5)
View Python source: neopyxel.py
#######
#
# Module: NeoPixel (WS2812) Driver using PIO
# Based on open-source WS2812 PIO examples from the Raspberry Pi Foundation
# and MicroPython community.
#
# Adapted and documented by Denis Legault
# Date: 2026-02-10
# Python File: neopixel.py
# Hardware: Raspberry Pi Pico / Pico 2 W,
# WS2812 / NeoPixel LED strip
#
# Description:
# Provides a custom NeoPixel driver using the RP2040/RP2350 PIO (Programmable
# I/O) subsystem for precise timing control required by WS2812 LEDs.
#
# The module defines:
# - A PIO assembly routine (ws2812) to generate the required waveform
# - A myNeopixel class to manage LED buffers, brightness control,
# gradients, rotation, and display updates
#
# NeoPixels require strict timing (800kHz data stream). This implementation
# offloads signal generation to PIO hardware, ensuring accurate and stable
# communication without blocking the CPU.
#
# Notes:
# - Uses 24-bit GRB color format (8 bits per channel).
# - Brightness is software-scaled before transmission.
# - Requires 5V power supply for most LED strips.
# - Ground must be shared between Pico and LED strip.
#
#######
import array, time
from machine import Pin
import rp2
# ------------------------------------------------------------
# PIO Assembly Program for WS2812
# ------------------------------------------------------------
# Generates precise timing signals required by NeoPixel LEDs.
# This runs independently in hardware (PIO state machine).
# ------------------------------------------------------------
@rp2.asm_pio(
sideset_init=rp2.PIO.OUT_LOW,
out_shiftdir=rp2.PIO.SHIFT_LEFT,
autopull=True,
pull_thresh=24
)
def ws2812():
T1 = 2
T2 = 5
T3 = 8
wrap_target()
label("bitloop")
out(x, 1) .side(0) [T3 - 1]
jmp(not_x, "do_zero") .side(1) [T1 - 1]
jmp("bitloop") .side(1) [T2 - 1]
label("do_zero")
nop() .side(0) [T2 - 1]
wrap
# ------------------------------------------------------------
# NeoPixel Class
# ------------------------------------------------------------
class myNeopixel:
def __init__(self, num_leds, pin, delay_ms=1):
"""
Initialize NeoPixel driver.
:param num_leds: Number of LEDs in the strip
:param pin: GPIO pin used for data output
:param delay_ms: Delay after updating LEDs (stability)
"""
# Create pixel buffer (32-bit integers per LED)
self.pixels = array.array("I", [0 for _ in range(num_leds)])
# Use State Machine 0
self.state_machine = 0
# Initialize PIO state machine with 8 MHz frequency
self.sm = rp2.StateMachine(
self.state_machine,
ws2812,
freq=8000000,
sideset_base=Pin(pin)
)
# Activate state machine
self.sm.active(1)
self.num_leds = num_leds
self.delay_ms = delay_ms
self.brightnessvalue = 255 # Default full brightness
# --------------------------------------------------------
# Brightness Control
# --------------------------------------------------------
def brightness(self, brightness=None):
"""
Get or set global brightness (1β255).
"""
if brightness is None:
return self.brightnessvalue
else:
if brightness < 1:
brightness = 1
if brightness > 255:
brightness = 255
self.brightnessvalue = brightness
# --------------------------------------------------------
# Gradient between two pixels
# --------------------------------------------------------
def set_pixel_line_gradient(
self,
pixel1,
pixel2,
left_r,
left_g,
left_b,
right_r,
right_g,
right_b
):
"""
Create a color gradient between two pixel positions.
"""
if pixel2 - pixel1 == 0:
return
right_pixel = max(pixel1, pixel2)
left_pixel = min(pixel1, pixel2)
for i in range(right_pixel - left_pixel + 1):
fraction = i / (right_pixel - left_pixel)
red = round((right_r - left_r) * fraction + left_r)
green = round((right_g - left_g) * fraction + left_g)
blue = round((right_b - left_b) * fraction + left_b)
self.set_pixel(left_pixel + i, red, green, blue)
# --------------------------------------------------------
# Set solid color across a pixel range
# --------------------------------------------------------
def set_pixel_line(self, pixel1, pixel2, r, g, b):
for i in range(pixel1, pixel2 + 1):
self.set_pixel(i, r, g, b)
# --------------------------------------------------------
# Set individual pixel color
# --------------------------------------------------------
def set_pixel(self, pixel_num, r, g, b):
"""
Set a single pixel color with brightness scaling.
WS2812 uses GRB format internally.
"""
# Apply brightness scaling
blue = round(b * (self.brightness() / 255))
red = round(r * (self.brightness() / 255))
green = round(g * (self.brightness() / 255))
# Store packed GRB value
self.pixels[pixel_num] = blue | red << 8 | green << 16
# --------------------------------------------------------
# Rotate pixel buffer left
# --------------------------------------------------------
def rotate_left(self, num_of_pixels):
if num_of_pixels is None:
num_of_pixels = 1
self.pixels = self.pixels[num_of_pixels:] + self.pixels[:num_of_pixels]
# --------------------------------------------------------
# Rotate pixel buffer right
# --------------------------------------------------------
def rotate_right(self, num_of_pixels):
if num_of_pixels is None:
num_of_pixels = 1
num_of_pixels = -1 * num_of_pixels
self.pixels = self.pixels[num_of_pixels:] + self.pixels[:num_of_pixels]
# --------------------------------------------------------
# Send pixel data to LED strip
# --------------------------------------------------------
def show(self):
"""
Transmit pixel buffer to LEDs via PIO.
"""
for i in range(self.num_leds):
self.sm.put(self.pixels[i], 8)
# Small delay ensures data latch timing
time.sleep_ms(self.delay_ms)
# --------------------------------------------------------
# Fill entire strip with one color
# --------------------------------------------------------
def fill(self, r, g, b):
"""
Set all LEDs to a single color.
"""
for i in range(self.num_leds):
self.set_pixel(i, r, g, b)
time.sleep_ms(self.delay_ms)
Video showing the Raspberry Pi Pico 2W controlling a moving rainbow animation across a NeoPixel strip. A colour wheel function generates smooth RGB transitions, and each LED is offset slightly to create a flowing rainbow effect.
View Python source: main.py
#######
#
# Project: NeoPixel Rainbow Animation
# Based on open-source WS2812 PIO examples from the Raspberry Pi Foundation
# and MicroPython community.
#
# Adapted and documented by Denis Legault
# Date: 2026-02-12
# Python File: main.py
# Hardware: Raspberry Pi Pico 2 W,
# 8x WS2812 / NeoPixel LEDs connected to GPIO16
#
# Description:
# Demonstrates a moving rainbow animation across a NeoPixel strip.
# A color wheel function generates smooth RGB transitions, and each
# LED is offset slightly to create a flowing rainbow effect.
#
# The animation continuously cycles through 0β254 color positions,
# producing a seamless moving rainbow pattern.
#
# GPIO Pin Connections:
# NeoPixel Data In -> GPIO16
# 5V -> External 5V supply (recommended)
# GND -> Common ground with Pico
#
# Notes:
# - Requires neopixel.py containing the myNeopixel class.
# - Uses 24-bit RGB color values (0β255 per channel).
# - Brightness is limited to reduce current draw.
# - Saved as main.py to auto-run on power-up.
#
#######
from machine import Pin
from neopixel import myNeopixel
import time
# Number of LEDs in the strip
NUM_LEDS = 8
# Initialize NeoPixel object on GPIO16
np = myNeopixel(NUM_LEDS, 16)
# Global RGB values updated by wheel()
red = 0
green = 0
blue = 0
# ------------------------------------------------------------
# Color Wheel Function
# ------------------------------------------------------------
# Generates smooth RGB transitions across a 0β254 range.
# This creates a full rainbow spectrum.
# ------------------------------------------------------------
def wheel(pos):
global red, green, blue
WheelPos = pos % 255 # Ensure value wraps around 0β254
# Red -> Green transition
if WheelPos < 85:
red = (255 - WheelPos * 3)
green = (WheelPos * 3)
blue = 0
# Green -> Blue transition
elif WheelPos < 170:
WheelPos -= 85
red = 0
green = (255 - WheelPos * 3)
blue = (WheelPos * 3)
# Blue -> Red transition
else:
WheelPos -= 170
red = (WheelPos * 3)
green = 0
blue = (255 - WheelPos * 3)
# Set global brightness (0β255)
np.brightness(20) # Lower brightness reduces power consumption
while True:
# Cycle through full color wheel range
for i in range(0, 255):
# Update each LED with an offset to create moving effect
for j in range(0, NUM_LEDS):
wheel(i + j * 255 // NUM_LEDS)
np.set_pixel(j, red, green, blue)
# Send updated pixel data to strip
np.show()
# Short delay controls animation speed
time.sleep_ms(1)
See previous video above for neopixel.py code>
While working on the Freenove 4WD Smart Car, I ran into an issue controlling the onboard WS2812 (NeoPixel) LEDs using a Raspberry Pi 5. The LEDs would partially light or behave unpredictably, suggesting a timing-related problem rather than a hardware fault.
After learning more about how WS2812 LEDs work β specifically their strict 800 kHz pulse-width timing requirements β I experimented with using a Raspberry Pi Pico 2 W instead of the Raspberry Pi 5.
I disconnected the Pi 5 from the LED data line (GPIO10 on PCB V2.0) and connected the Pico directly to the WS2812 data input. Using the Picoβs PIO (Programmable I/O) hardware, I was able to generate precise timing signals and successfully control all 8 LEDs, including smooth animations such as rainbow effects and a Knight Rider scanner pattern.
This experiment proved that:
- The LED hardware and wiring were functioning correctly.
- The issue was not power-related.
- The problem was due to timing sensitivity when generating the WS2812 signal from Linux on the Raspberry Pi 5.
By offloading LED control to the Pico β a microcontroller designed for deterministic, real-time signal generation β the LEDs operated flawlessly.
Use a microcontroller for precise timing tasks, and a Linux-based system for high-level control and networking.
The Pico could be used as a dedicated LED coprocessor for the 4WD car.
Hexadecimal counter (0βF) displayed on a 7-segment LED using a 74HC595 shift register and Raspberry Pi Pico 2 W. The decimal point flashes for each digit. Demonstrates bit-level control and serial-to-parallel output expansion.
View Python source: main.py
#######
#
# Project: 7-Segment Counter with Flashing Decimal Point
# Based on common 74HC595 shift register usage patterns and
# 7-segment display encoding examples.
#
# Adapted and documented by Denis Legault
# Date: 2026-03-01
# Python File: main.py
# Hardware: Raspberry Pi Pico 2 W,
# 74HC595 Shift Register,
# 1x Common Anode 7-Segment Display
#
# Description:
# Displays hexadecimal digits (0βF) on a 7-segment display
# using a 74HC595 shift register. The decimal point (DP)
# flashes ON and OFF for each digit.
#
# Display Type:
# Common Anode (Active LOW)
# - 0 = Segment ON
# - 1 = Segment OFF
#
# Bit Layout (based on wiring configuration):
# Bit 7 β DP (Decimal Point)
# Bit 6 β Segment A
# Bit 5 β Segment B
# Bit 4 β Segment C
# Bit 3 β Segment D
# Bit 2 β Segment E
# Bit 1 β Segment F
# Bit 0 β Segment G
#
# Notes:
# - Uses LSB-first shifting via 74HC595.
# - Decimal point controlled via bit mask (0x80).
#
#######
import time
from my74HC595 import Chip74HC595
# ------------------------------------------------------------
# 7-Segment Encoding Table (Hexadecimal 0βF)
# ------------------------------------------------------------
SEGMENT_MAP = [
0xC0, # 0
0xF9, # 1
0xA4, # 2
0xB0, # 3
0x99, # 4
0x92, # 5
0x82, # 6
0xF8, # 7
0x80, # 8
0x90, # 9
0x88, # A
0x83, # b
0xC6, # C
0xA1, # d
0x86, # E
0x8E # F
]
# ------------------------------------------------------------
# Decimal Point Helper (Active LOW)
# ------------------------------------------------------------
def set_dp(value, on):
"""
Enable or disable decimal point.
Common Anode display β active LOW.
"""
if on:
return value & ~0x80 # Clear bit 7 β DP ON
else:
return value | 0x80 # Set bit 7 β DP OFF
# Initialize 74HC595
chip = Chip74HC595(18, 20, 21)
# ------------------------------------------------------------
# Main Loop
# ------------------------------------------------------------
while True:
for count in range(16):
value = SEGMENT_MAP[count]
# Display digit with DP ON
chip.shiftOut(0, set_dp(value, True))
time.sleep(0.5)
# Display digit with DP OFF
chip.shiftOut(0, set_dp(value, False))
time.sleep(0.5)
View Python source: my74HC595.py
#######
#
# Module: 74HC595 Shift Register Driver
# Based on common 74HC595 shift register usage patterns and
# MicroPython GPIO examples.
#
# Adapted and documented by Denis Legault
# Date: 2026-03-01
# Python File: chip74hc595.py
# Hardware: Raspberry Pi Pico 2 W,
# 74HC595 8-bit shift register
#
# Description:
# Provides a simple MicroPython driver for controlling a 74HC595
# serial-in / parallel-out shift register using GPIO pins.
#
# The class allows:
# - Shifting out 8-bit values (MSB-first or LSB-first)
# - Clearing the register
# - Enabling and disabling output
#
# The 74HC595 is commonly used to expand GPIO output pins,
# allowing control of multiple LEDs or digital outputs using
# only three control lines from the Pico.
#
# 74HC595 Pin Connections:
# 14 (DS) -> Pico GPIO18 (Serial Data)
# 12 (STCP) -> Pico GPIO20 (Storage Register Clock / Latch)
# 11 (SHCP) -> Pico GPIO21 (Shift Register Clock)
# 13 (OE) -> Pico GPIO19 (Output Enable, active LOW)
# 16 (VCC) -> 5V or 3.3V (depending on design)
# 8 (GND) -> Ground (shared with Pico)
#
# Notes:
# - Outputs Q0βQ7 update only after latch (STCP) pulse.
# - OE is active LOW (LOW = outputs enabled).
# - Multiple 74HC595 chips can be daisy-chained.
#
#######
from machine import Pin
class Chip74HC595(object):
def __init__(self, ds: int = 18, stcp: int = 20, shcp: int = 21, oe: int = 19):
"""
Initialize 74HC595 control pins.
:param ds: Serial Data input (DS)
:param stcp: Storage Register Clock (Latch)
:param shcp: Shift Register Clock
:param oe: Output Enable (active LOW)
"""
# Configure control pins as outputs
self._ds = Pin(ds, Pin.OUT, value=0) # Data pin
self._shcp = Pin(shcp, Pin.OUT, value=0) # Shift clock
self._stcp = Pin(stcp, Pin.OUT, value=0) # Latch clock
self._oe = Pin(oe, Pin.OUT, value=0) # Output enable
# Enable outputs by default
self.enable()
# --------------------------------------------------------
# Shift out 8 bits of data
# --------------------------------------------------------
def shiftOut(self, direction, data):
"""
Shift out one byte (8 bits) to the register.
:param direction: True = MSB first, False = LSB first
:param data: 8-bit integer value (0β255)
"""
# Prepare clocks
self._shcp.on()
self._stcp.on()
# MSB first
if direction:
for i in range(8):
bit = data << i
bit = bit & 0x80
if bit == 0x80:
self._ds.on()
else:
self._ds.off()
self._shift_bit()
self._send_data()
# LSB first
if not direction:
for i in range(8):
bit = data >> i
bit = bit & 0x01
if bit == 0x01:
self._ds.on()
else:
self._ds.off()
self._shift_bit()
self._send_data()
# --------------------------------------------------------
# Clear all outputs
# --------------------------------------------------------
def clear(self):
"""
Clear the shift register (all outputs LOW).
"""
for i in range(8):
self._ds.off()
self._shift_bit()
self._send_data()
self.enable()
# --------------------------------------------------------
# Pulse shift clock (SHCP)
# --------------------------------------------------------
def _shift_bit(self):
"""
Clock a single bit into the shift register.
"""
self._shcp.off()
self._shcp.on()
# --------------------------------------------------------
# Pulse latch clock (STCP)
# --------------------------------------------------------
def _send_data(self):
"""
Latch shifted data to output pins Q0βQ7.
"""
self._stcp.off()
self._stcp.on()
# --------------------------------------------------------
# Output Enable Control
# --------------------------------------------------------
def disable(self):
"""
Disable outputs (OE HIGH).
"""
self._oe.on()
def enable(self):
"""
Enable outputs (OE LOW).
"""
self._oe.off()