Infineon Team
Published © MIT

Magic Pages - A Self-turning Book with Audio ✨

Make your favourite Christmas story come to life!

IntermediateFull instructions provided24 hours261

Things used in this project

Hardware components

Pre-Crimped Wires
×1
Veroboard
×1
JST-XH Connectors
×1
Infineon PSOC™ 6 Wi-Fi BT Prototyping Kit
×1
5V Power Supply
×1
DY-SV5W Voice Playback Module
×1
4 M2x8mm screws
×1
4 M1.5x8mm screws
×1
16 M3x20mm screws
×1
4 M4x10mm screws
×1
Speaker, 4 Ω, 5 W
×1

Software apps and online services

MicroPython
MicroPython
Thonny

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)
Soldering iron (generic)
Soldering iron (generic)
Solder Wire, Lead Free
Solder Wire, Lead Free

Story

Read more

Custom parts and enclosures

Page Holder 1

Page Holder 2

Page Holder 3

Page Holder 4

Title in Gold

Motor Mount

Sketchfab still processing.

Book Case

Page 1

Page 2

Page 3

Page 4

Sketch of Case

Don't print, just for reference

Audio Page 1

Audio Page 2

Audio Page 3

Audio Page 4

Audio Page 5

Audio Page 6

Audio Page 7

Page Designs 1-7

Powerpoint of all the book pages, print out in colour and cut to size. :)

Schematics

Fritzing

Code

Main Code

MicroPython
Code with Audio and Motor Control
# Import library needed for motor control
from machine import PWM
#Import libraries needed for audio control
from machine import Pin
import time


'''MOTOR CONTROL:'''
# Set parameters
pw_min = 500000 #ns
pw_max = 2500000 #ns
angle_min = 0 #degrees
angle_max = 180 #degrees
frequency = 50 #Hz

# Initialize PWM for all motors, set intial angle to 0°
motor1 = PWM('P5_7', freq=frequency, duty_ns=pw_min)
motor2 = PWM('P5_6', freq=frequency, duty_ns=pw_min)
motor3 = PWM('P5_5', freq=frequency, duty_ns=pw_min)
motor4 = PWM('P5_4', freq=frequency, duty_ns=pw_min)

'''Auxiliary function,
   Input: angle
   Output: pulse width'''
def angle_to_ns(angle):
    # Check if angle is in range
    if(angle_min <= angle and angle <= angle_max):
        # In range -> Calculate pulse width 
        angle_percentage = (angle - angle_min)/(angle_max - angle_min)
        ns_percentage = angle_percentage
        ns = ns_percentage*(pw_max - pw_min) + pw_min
        # Print ns to help with troubleshooting later on
        return ns
    else:
        # Not in range -> Return error
        print("Error: Angle not in range!")
        return False
        
'''Input: Angle and motor
   Calculates pulse width and sends signal to respective motor'''
def set_motor_to_angle(motor, angle):
    # Gets the pulse width from auxiliary function
    ns = angle_to_ns(angle)
    if (ns is False): return
    # Convert float to int 
    ns = int(ns)
    # Check if the conversion has pushed the pulse width out of bounds
    #-> adjust accordingly 
    if(ns > pw_max):
        ns = pw_max
    elif (ns < pw_min):
        ns = pw_min
    # Send signal to motor
    motor.duty_ns(ns)
    
'''Input: motor and motor number
   Turn page right to left,
   Change angle in increments'''
def page_right2left(motor, number):
    increment = 1
    # Set angle counter to 0
    j = 0
    # Print user info
    print("turning motor number", number)
    while (j <= 180):
        time.sleep(0.03)
        set_motor_to_angle(motor,j)
        j = j+increment

'''AUDIO CONTROL:'''

'''Set all pins'''
# Busy pin,high when audio is playing, low when it isn't
busy = Pin('P8_0', Pin.IN)

# GPIO input pins, initialize with 1 (no audio)
pin_0 = Pin('P9_7', Pin.OUT, value = 1)
pin_1 = Pin('P9_4', Pin.OUT, value = 1)
pin_2 = Pin('P9_1', Pin.OUT, value = 1)
pin_3 = Pin('P9_2', Pin.OUT, value = 1)

# Make list of pins
pins = [pin_0, pin_1, pin_2, pin_3]

# Amount of pins (max 8!)
am_p = 4 
# Amount of tracks
am_t = 2**am_p

'''Input: String that represents a binary number (track number)
   Plays that track once'''
def play_track_once_000(bin_str):
    # Make copy of input
    bin_str_2 = bin_str
    
    # Sequentially go through each character in the String
    for i in range(len(bin_str)):
        # Get last character in string
        last_ch = bin_str_2[-1]
        # Invert number (As defined by I/O integrated mode 0 on the DY-SV5W audio module)
        # Set pin to that value
        if (last_ch == '0'):
            pins[i].value(1)
            # Print user info for error finding
            print("pin:",i,"value: 1")
        else:
            pins[i].value(0)
            # Print user info for error finding
            print("pin:",i,"value: 0")
            
        # Remove character we just checked (last character in string)
        bin_str_2 = bin_str_2[:-1] if i < len(bin_str_2) else bin_str_2
    
    # Wait for 20ms
    time.sleep_ms(20)
    
    # Wait until audio module starts playing (busy pin goes to HIGH)
    while (busy.value() == 0):
        time.sleep_ms(50)  
            
    # Once audio is playing -> reset all pins to default HIGH state
    # This avoids the track being replayed after it finishes
    for i in range(am_p):
        print("Set pin", i,"back to 1")
        pins[i].value(1)
           
    # Wait until audio has finished playing (busy pin goes to LOW)
    while (busy.value() == 1):
        time.sleep_ms(50) 
    
   
'''Input: track number to play
   Convert track nr to binary number (bin function returns String)
   Call play_track_once'''
def play_track_number(number):
    # Check if number in range
    if (number < 0 or number > am_t-1):
        # Print error message
        print("Error: number not in range, only", am_t, "tracks")
        return
    # Convert track number to binary number
    binary_num = bin(number)
    print("After bin():", binary_num, "type:", type(binary_num))
    # Remove binary prefix
    binary_num = binary_num[2:]
    print("After removing prefix:", binary_num, "type:", type(binary_num))
    
    #Call function to set pins and play track
    play_track_once_000(binary_num)
    
'''MAIN CODE:'''
page_right2left(motor1, 1)
play_track_number(1)
play_track_number(2)
page_right2left(motor2, 2)
play_track_number(3)
play_track_number(4)
page_right2left(motor3, 3)
play_track_number(5)
play_track_number(6)
page_right2left(motor4, 4)
play_track_number(7)

Audio Code

MicroPython
Code for Audio Control
#Import libraries
from machine import Pin
import time

'''Set all pins'''
# Busy pin,high when audio is playing, low when it isn't
busy = Pin('P8_0', Pin.IN)

# GPIO input pins, initialize with 1 (no audio)
pin_0 = Pin('P9_7', Pin.OUT, value = 1)
pin_1 = Pin('P9_4', Pin.OUT, value = 1)
pin_2 = Pin('P9_1', Pin.OUT, value = 1)
pin_3 = Pin('P9_2', Pin.OUT, value = 1)

# Make list of pins
pins = [pin_0, pin_1, pin_2, pin_3]

# Amount of pins (max 8!)
am_p = 4 
# Amount of tracks
am_t = 2**am_p

'''Input: String that represents a binary number (track number)
   Plays that track once'''
def play_track_once_000(bin_str):
    # Make copy of input
    bin_str_2 = bin_str
    
    # Sequentially go through each character in the String
    for i in range(len(bin_str)):
        # Get last character in string
        last_ch = bin_str_2[-1]
        # Invert number (As defined by I/O integrated mode 0 on the DY-SV5W audio module)
        # Set pin to that value
        if (last_ch == '0'):
            pins[i].value(1)
            # Print user info for error finding
            print("pin:",i,"value: 1")
        else:
            pins[i].value(0)
            # Print user info for error finding
            print("pin:",i,"value: 0")
            
        # Remove character we just checked (last character in string)
        bin_str_2 = bin_str_2[:-1] if i < len(bin_str_2) else bin_str_2
    
    # Wait for 20ms
    time.sleep_ms(20)
    
    # Wait until audio module starts playing (busy pin goes to HIGH)
    while (busy.value() == 0):
        time.sleep_ms(50)  
            
    # Once audio is playing -> reset all pins to default HIGH state
    # This avoids the track being replayed after it finishes
    for i in range(am_p):
        print("Set pin", i,"back to 1")
        pins[i].value(1)
           
    # Wait until audio has finished playing (busy pin goes to LOW)
    while (busy.value() == 1):
        time.sleep_ms(50) 
    
   
'''Input: track number to play
   Convert track nr to binary number (bin function returns String)
   Call play_track_once'''
def play_track_number(number):
    # Check if number in range
    if (number < 0 or number > am_t-1):
        # Print error message
        print("Error: number not in range, only", am_t, "tracks")
        return
    # Convert track number to binary number
    binary_num = bin(number)
    print("After bin():", binary_num, "type:", type(binary_num))
    # Remove binary prefix
    binary_num = binary_num[2:]
    print("After removing prefix:", binary_num, "type:", type(binary_num))
    
    #Call function to set pins and play track
    play_track_once_000(binary_num)

Motor Code

MicroPython
Code for Motor Control
# Import libraries needed for pin communication and time measurement
from machine import PWM
import time

# Set parameters
pw_min = 500000 #ns
pw_max = 2500000 #ns
angle_min = 0 #degrees
angle_max = 180 #degrees
frequency = 50 #Hz

# Initialize PWM for all motors, set intial angle to 0°
motor1 = PWM('P5_7', freq=frequency, duty_ns=pw_min)
motor2 = PWM('P5_6', freq=frequency, duty_ns=pw_min)
motor3 = PWM('P5_5', freq=frequency, duty_ns=pw_min)
motor4 = PWM('P5_4', freq=frequency, duty_ns=pw_min)

'''Auxiliary function,
   Input: angle
   Output: pulse width'''
def angle_to_ns(angle):
    # Check if angle is in range
    if(angle_min <= angle and angle <= angle_max):
        # In range -> Calculate pulse width 
        angle_percentage = (angle - angle_min)/(angle_max - angle_min)
        ns_percentage = angle_percentage
        ns = ns_percentage*(pw_max - pw_min) + pw_min
        # Print ns to help with troubleshooting later on
        return ns
    else:
        # Not in range -> Return error
        print("Error: Angle not in range!")
        return False
        
'''Input: Angle and motor
   Calculates pulse width and sends signal to respective motor'''
def set_motor_to_angle(motor, angle):
    # Gets the pulse width from auxiliary function
    ns = angle_to_ns(angle)
    if (ns is False): return
    # Convert float to int 
    ns = int(ns)
    # Check if the conversion has pushed the pulse width out of bounds
    #-> adjust accordingly 
    if(ns > pw_max):
        ns = pw_max
    elif (ns < pw_min):
        ns = pw_min
    # Send signal to motor
    motor.duty_ns(ns)
    

'''Input: motor and motor number
   Turn page right to left,
   Change angle in increments'''
def page_right2left(motor, number):
    increment = 1
    # Set angle counter to 0
    j = 0
    # Print user info
    print("turning motor number", number)
    while (j <= 180):
        time.sleep(0.03)
        set_motor_to_angle(motor,j)
        j = j+increment
        

Credits

Infineon Team
120 projects • 195 followers
Hands-on projects, tutorials, and code for sensors, MCUs, connectivity, security, power, and IoT. Follow for new builds and ideas.

Comments