Difference between revisions of "RaspberryPi:ProgrammingInPython"

From UCT EE Wiki
Jump to navigation Jump to search
m (CRNKEE002 moved page RaspberryPi:Programming to RaspberryPi:ProgrammingInPython without leaving a redirect: Going to split C and Python)
 
(13 intermediate revisions by 2 users not shown)
Line 1: Line 1:
 
[[Category:RaspberryPi]]
 
[[Category:RaspberryPi]]
 
= Overview =
 
= Overview =
The Raspberry Pi can run a Linux based operating system, and can therefore use almost any programming language. For the sake of interfacing with the GPIOs, Python and C/C++ are usually considered the simplest, and you will find the most resources
 
 
= Programming in Python =
 
 
Most of the content in the guide comes from the documentation, available online [https://sourceforge.net/p/raspberry-gpio-python/wiki/Home/ here].
 
Most of the content in the guide comes from the documentation, available online [https://sourceforge.net/p/raspberry-gpio-python/wiki/Home/ here].
  
Python, while not as powerful as C or C++, is quickly becoming a common choice for embedded systems developers due to its ease of use and rapid development times. See [https://spectrum.ieee.org/at-work/innovation/the-2018-top-programming-languages this IEEE article].
+
Python, while not as powerful as C or C++, is quickly becoming a common choice for embedded systems developers due to its ease of use and rapid development times. See [https://spectrum.ieee.org/at-work/innovation/the-2018-top-programming-languages this IEEE article]. For information on programming in C or C++, go to [[RaspberryPi:ProgrammingInC]].
  
 
This chapter serves as a short guide for programming in Python on the RPi. You will also find some templates for techniques such as debouncing, or making use of the Raspberry Pi’s multicore architecture by implementing threading.
 
This chapter serves as a short guide for programming in Python on the RPi. You will also find some templates for techniques such as debouncing, or making use of the Raspberry Pi’s multicore architecture by implementing threading.
  
== The RPI.GPIO Library ==
+
= The RPI.GPIO Library =
  
 
The RPi.GPIO library is the common Python library used on the Raspberry Pi. Documentation for the library can be found here:<br />
 
The RPi.GPIO library is the common Python library used on the Raspberry Pi. Documentation for the library can be found here:<br />
Line 17: Line 14:
 
It is included in the environment variables by default, so to use it, you can simply just import it into your Python script:
 
It is included in the environment variables by default, so to use it, you can simply just import it into your Python script:
  
<pre>include RPi.GPIO as GPIO</pre>
+
<syntaxhighlight lang="python">import RPi.GPIO as GPIO</syntaxhighlight>
You need to specify which board mode you’re using. For more information on board modes, see Section [sec:BoardModes]. Board modes are specified as follows:
+
You need to specify which board mode you’re using. For more information on board modes, see Section [[RaspberryPi:Overview#GPIO_Pins]]. Board modes are specified as follows:
  
<pre>GPIO.setmode(GPIO.BOARD)
+
<syntaxhighlight lang="python">GPIO.setmode(GPIO.BOARD)
 
# or
 
# or
GPIO.setmode(GPIO.BCM)</pre>
+
GPIO.setmode(GPIO.BCM)</syntaxhighlight>
 
You also need to perform “cleanup” on GPIOs when your application exists. By performing cleanup and resetting all pins to their default modes, you can prevent possible damage to your Raspberry Pi. The cleanup function is called as follows:
 
You also need to perform “cleanup” on GPIOs when your application exists. By performing cleanup and resetting all pins to their default modes, you can prevent possible damage to your Raspberry Pi. The cleanup function is called as follows:
  
<pre>GPIO.cleanup()</pre>
+
<syntaxhighlight lang="python">GPIO.cleanup()</syntaxhighlight>
 
If your program is meant to run indefinitely, and only close upon an exception, you can wrap it in a try catch, as follows:
 
If your program is meant to run indefinitely, and only close upon an exception, you can wrap it in a try catch, as follows:
  
<pre>if __name__ == &quot;__main__&quot;:
+
<syntaxhighlight lang="python">if __name__ == "__main__":
 
     # Make sure the GPIO is stopped correctly
 
     # Make sure the GPIO is stopped correctly
 
     try:
 
     try:
Line 34: Line 31:
 
             main()
 
             main()
 
     except KeyboardInterrupt:
 
     except KeyboardInterrupt:
         print(&quot;Exiting gracefully&quot;)
+
         print("Exiting gracefully")
 
         GPIO.cleanup()
 
         GPIO.cleanup()
 
     except e:
 
     except e:
         print(&quot;Some other error occurred: {}&quot;.format(e.message)})
+
         print("Some other error occurred: {}".format(e.message)})
         GPIO.cleanup()</pre>
+
         GPIO.cleanup()</syntaxhighlight>
== Basic IO ==
+
 
 +
= Basic IO =
  
 
Read the documentation, available [https://sourceforge.net/p/raspberry-gpio-python/wiki/Outputs/ here].
 
Read the documentation, available [https://sourceforge.net/p/raspberry-gpio-python/wiki/Outputs/ here].
  
=== Digital Logic ===
+
== Digital Logic ==
  
 
Digital output on the Raspberry Pi is accomplished by writing values to channels. A channel is a pin, which is numbered in the way you’ve configured. Basic configuration of a pin for output is as follows:
 
Digital output on the Raspberry Pi is accomplished by writing values to channels. A channel is a pin, which is numbered in the way you’ve configured. Basic configuration of a pin for output is as follows:
  
<pre>GPIO.output(&lt;channel&gt;, &lt;Logic&gt;)</pre>
+
<syntaxhighlight lang="python">GPIO.output(<channel>, <Logic>)</syntaxhighlight>
 
Your logic (high, 3.3V, or low, 0V), can be specified as follows:
 
Your logic (high, 3.3V, or low, 0V), can be specified as follows:
  
 
<ul>
 
<ul>
 
<li><p>For 3.3V (high) output:</p>
 
<li><p>For 3.3V (high) output:</p>
<pre>           GPIO.output(&lt;channel&gt;, GPIO.HIGH)
+
<syntaxhighlight lang="python">
            GPIO.output(&lt;channel&gt;, 1)
+
GPIO.output(<channel>, GPIO.HIGH)
            GPIO.output(&lt;channel&gt;, True)
+
GPIO.output(<channel>, 1)
        </pre></li>
+
GPIO.output(<channel>, True)
 +
</syntaxhighlight></li>
 +
 
 
<li><p>For 0V (low) output:</p>
 
<li><p>For 0V (low) output:</p>
<pre>           GPIO.output(&lt;channel&gt;, GPIO.LOW)
+
<syntaxhighlight lang="python">
            GPIO.output(&lt;channel&gt;, 0)
+
GPIO.output(<channel>, GPIO.LOW)
            GPIO.output(&lt;channel&gt;, False)
+
GPIO.output(<channel>, 0)
        </pre></li></ul>
+
GPIO.output(<channel>, False)
 +
</syntaxhighlight></li></ul>
  
 
Channels can also be specified in lists, for example:
 
Channels can also be specified in lists, for example:
  
<pre>LEDs = (11,12)
+
<syntaxhighlight lang="python">LEDs = (11,12)
 
GPIO.output(LEDs, GPIO.HIGH) # Will turn all channels HIGH
 
GPIO.output(LEDs, GPIO.HIGH) # Will turn all channels HIGH
GPIO.output(LEDs, (GPIO.HIGH, GPIO.LOW)) # Will the first channel HIGH, and the second LOW</pre>
+
GPIO.output(LEDs, (GPIO.HIGH, GPIO.LOW)) # Will the first channel HIGH, and the second LOW</syntaxhighlight>
 +
 
 
Note that you can also read the state of the pin/channel that is set as an output by using the <code>input()</code> function. For example, if you wanted to toggle pin 12, you could do something as follows:
 
Note that you can also read the state of the pin/channel that is set as an output by using the <code>input()</code> function. For example, if you wanted to toggle pin 12, you could do something as follows:
  
<pre>GPIO.output(12, not GPIO.input(12))</pre>
+
<syntaxhighlight lang="python">GPIO.output(12, not GPIO.input(12))</syntaxhighlight>
=== Analog ===
+
 
 +
== Analog ==
  
 
The Raspberry Pi does not have any analog input pins. You will need to use something like the [https://cdn-shop.adafruit.com/datasheets/MCP3008.pdf MCP3008] to read analog voltages.
 
The Raspberry Pi does not have any analog input pins. You will need to use something like the [https://cdn-shop.adafruit.com/datasheets/MCP3008.pdf MCP3008] to read analog voltages.
  
=== Inputs ===
+
== Inputs ==
  
==== Digital Read ====
+
=== Digital Read ===
  
 
To read the value of a digital pin, you can use the <code>input()</code> function:
 
To read the value of a digital pin, you can use the <code>input()</code> function:
  
<pre>if GPIO.input(12):
+
<syntaxhighlight lang="python">if GPIO.input(12):
     print(&quot;Pin 12 HIGH&quot;)
+
     print("Pin 12 HIGH")
 
else:
 
else:
     print(&quot;Pin 12 LOW&quot;)</pre>
+
     print("Pin 12 LOW")</syntaxhighlight>
==== Pull Up/Pull Down Resistors ====
+
=== Pull Up/Pull Down Resistors ===
  
 
Using pull up and pull down resistors is essential when working with digital logic. Thankfully, the Raspberry Pi has internal pull up and pull down resistors. To make use of these, initialize the pin/channel as follows:
 
Using pull up and pull down resistors is essential when working with digital logic. Thankfully, the Raspberry Pi has internal pull up and pull down resistors. To make use of these, initialize the pin/channel as follows:
  
<pre>GPIO.setup(&lt;channel&gt;, GPIO.IN, pull_up_down=GPIO.PUD_UP)
+
<syntaxhighlight lang="python">GPIO.setup(<channel>, GPIO.IN, pull_up_down=GPIO.PUD_UP)
 
# or
 
# or
GPIO.setup(&lt;channel&gt;, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)</pre>
+
GPIO.setup(<channel>, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)</syntaxhighlight>
==== Interrupts ====
+
=== Interrupts ===
  
 
Very often it is useful to set up an interrupt, for example to trigger an event when a button is pressed. Callback functions are executed on a different thread. If you have multiple callbacks, know that they will be executed sequentially as only one thread exists for interrupts. Interrupts can be implemented as follows:
 
Very often it is useful to set up an interrupt, for example to trigger an event when a button is pressed. Callback functions are executed on a different thread. If you have multiple callbacks, know that they will be executed sequentially as only one thread exists for interrupts. Interrupts can be implemented as follows:
  
<pre># The bounce time is given in milliseconds.
+
<syntaxhighlight lang="python"># The bounce time is given in milliseconds.
 
# If your pin is set to use pull down resistors
 
# If your pin is set to use pull down resistors
# Connect a button between &lt;channel&gt; and 3.3V
+
# Connect a button between <channel> and 3.3V
GPIO.add_event_detect(&lt;channel&gt;, GPIO.RISING, callback=callback_method(), bouncetime=200)
+
GPIO.add_event_detect(<channel>, GPIO.RISING, callback=callback_method(), bouncetime=200)
 
# If your pin is set to use pull up resistors
 
# If your pin is set to use pull up resistors
# Connect a button between &lt;channel&gt; and GND
+
# Connect a button between <channel> and GND
GPIO.add_event_detect(&lt;channel&gt;, GPIO.FALLING, callback=callback_method(), bouncetime=200)  </pre>
+
GPIO.add_event_detect(<channel>, GPIO.FALLING, callback=callback_method(), bouncetime=200)  </syntaxhighlight>
==== Other functions ====
+
=== Other functions ===
  
 
The RPi.GPIO Library offers other functions for interrupts the first is the <code>wait_for_edge()</code> function, which is designed to block execution until an edge is detected. You can detect edges of <code>GPIO.RISING</code>, <code>GPIO.FALLING</code>, or <code>GPIO.BOTH</code>. The timeout is given in milliseconds:
 
The RPi.GPIO Library offers other functions for interrupts the first is the <code>wait_for_edge()</code> function, which is designed to block execution until an edge is detected. You can detect edges of <code>GPIO.RISING</code>, <code>GPIO.FALLING</code>, or <code>GPIO.BOTH</code>. The timeout is given in milliseconds:
  
<pre># wait for up to 5 seconds for a rising edge
+
<syntaxhighlight lang="python"># wait for up to 5 seconds for a rising edge
TO = GPIO.wait_for_edge(&lt;channel&gt;, GPIO_RISING, timeout=5000)
+
edge = GPIO.wait_for_edge(<channel>, GPIO_RISING, timeout=5000)
if TO is None:
+
if edge is None:
 
     print('Timeout occurred')
 
     print('Timeout occurred')
 
else:
 
else:
     print('Edge detected on pin ', TO)</pre>
+
     print('Edge detected on pin <CHANNEL>')</syntaxhighlight>
 +
 
 
Another function is the <code>event_detected()</code> function. From the docs: “The event_detected() function is designed to be used in a loop with other things, but unlike polling it is not going to miss the change in state of an input while the CPU is busy working on other things. This could be useful when using something like Pygame or PyQt where there is a main loop listening and responding to GUI events in a timely basis.”
 
Another function is the <code>event_detected()</code> function. From the docs: “The event_detected() function is designed to be used in a loop with other things, but unlike polling it is not going to miss the change in state of an input while the CPU is busy working on other things. This could be useful when using something like Pygame or PyQt where there is a main loop listening and responding to GUI events in a timely basis.”
  
<pre>GPIO.add_event_detect(channel, GPIO.RISING)  # add rising edge detection on a channel
+
<syntaxhighlight lang="python">GPIO.add_event_detect(channel, GPIO.RISING)  # add rising edge detection on a channel
do_something()
 
 
if GPIO.event_detected(channel):
 
if GPIO.event_detected(channel):
     print('Button pressed')</pre>
+
     print('Button pressed')</syntaxhighlight>
== Communication Protocols ==
+
 
 +
= Communication Protocols =
  
 
The RPi.GPIO library has no native support for communication protocols. Very often, specific Python libraries are provided on a per-device use case. However, this can become tedious and cause your code to become bloated. See the sections below for relevant libraries for using I2C and SPI directly.
 
The RPi.GPIO library has no native support for communication protocols. Very often, specific Python libraries are provided on a per-device use case. However, this can become tedious and cause your code to become bloated. See the sections below for relevant libraries for using I2C and SPI directly.
  
=== I2C ===
+
== I2C ==
  
 
Ensure you enable I2C in <code>raspi-config</code>.
 
Ensure you enable I2C in <code>raspi-config</code>.
Line 134: Line 138:
 
Once you connect a device, you can run <code>$gpio i2cdetect</code> to determine if a device is detected on the I2C bus. In the example below, there is an electronic compass (GY-271) connected to the Pi.
 
Once you connect a device, you can run <code>$gpio i2cdetect</code> to determine if a device is detected on the I2C bus. In the example below, there is an electronic compass (GY-271) connected to the Pi.
  
[[File:Figures/i2cdetect|frame|none|alt=|caption An i2cdetect example<span data-label="fig:i2cdetect"></span>]]
+
[[File:I2cdetect.png|frame|none|alt=|An i2cdetect example]]
  
 
To initialize an I2C device, do the following:
 
To initialize an I2C device, do the following:
  
 
<pre>import smbus
 
<pre>import smbus
REMEMBER = smbus.SMBus(1) # 1 indicates /dev/i2c-1
+
i2cdevice = smbus.SMBus(1) # 1 indicates /dev/i2c-1
 
address = 0x1e #whatever the device is for your i2c device</pre>
 
address = 0x1e #whatever the device is for your i2c device</pre>
 
To read a byte from the I2C device:
 
To read a byte from the I2C device:
  
<pre>result = REMEMBER.read_byte_data(address, &lt;register&gt;)</pre>
+
<pre>result = i2cdevice.read_byte_data(address, &lt;register&gt;)</pre>
 
To write a byte to the I2C device:
 
To write a byte to the I2C device:
  
<pre>REMEMBER.write_byte_data(address, &lt;register&gt;, &lt;value&gt;)</pre>
+
<pre>i2cdevice.write_byte_data(address, &lt;register&gt;, &lt;value&gt;)</pre>
==== Methods Available ====
+
=== Methods Available ===
  
 
<code>smbus</code> has multiple methods. The documentation for these is quite minimal, but brief descriptions are available [http://wiki.erazor-zone.de/wiki:linux:python:smbus:doc here].
 
<code>smbus</code> has multiple methods. The documentation for these is quite minimal, but brief descriptions are available [http://wiki.erazor-zone.de/wiki:linux:python:smbus:doc here].
  
=== SPI ===
+
Note: If you're working with EEPROM (specifically 24C32), you may need to read the answer to the questions given [https://www.raspberrypi.org/forums/viewtopic.php?p=1401819&sid=582cace49dad59d93f4e0369047dd89d#p1401819 here].
 +
 
 +
== SPI ==
  
 
Ensure you enable SPI in <code>raspi-config</code>.
 
Ensure you enable SPI in <code>raspi-config</code>.
Line 176: Line 182:
 
# Close the SPI connection
 
# Close the SPI connection
 
close()</pre>
 
close()</pre>
==== Settings ====
+
=== Settings ===
  
 
The following settings are configurable in the <code>spidev</code> library, and can be set as follows:
 
The following settings are configurable in the <code>spidev</code> library, and can be set as follows:
Line 197: Line 203:
 
SI/SO signals shared
 
SI/SO signals shared
  
==== Methods ====
+
=== Methods ===
  
 
The following methods are available in the <code>spidev</code> library
 
The following methods are available in the <code>spidev</code> library
  
* <code>open(bus, device)</code><br />
+
<ul>
 +
 
 +
<li><code>open(bus, device)</code><br>
 
Connects to the specified SPI device, opening <code>/dev/spidev&lt;bus&gt;.&lt;device&gt;</code>
 
Connects to the specified SPI device, opening <code>/dev/spidev&lt;bus&gt;.&lt;device&gt;</code>
* <code>readbytes(n)</code><br />
+
</li>
 +
<li><code>readbytes(n)</code><br>
 
Read n bytes from SPI device.
 
Read n bytes from SPI device.
* <code>writebytes(list of values)</code><br />
+
</li>
 +
<li><code>writebytes(list of values)</code><br />
 
Writes a list of values to SPI device.
 
Writes a list of values to SPI device.
* <code>writebytes2(list of values)</code><br />
+
</li>
 +
<li> <code>writebytes2(list of values)</code><br />
 
Similar to ‘writebytes‘ but accepts arbitrary large lists. If list size exceeds buffer size (which is read from <code>/sys/module/spidev/parameters/bufsiz</code>), data will be split into smaller chunks and sent in multiple operations. Also, writebytes2 understands [https://docs.python.org/3/c-api/buffer.html buffer protocol] so it can accept numpy byte arrays for example without need to convert them with <code>tolist()</code> first. This offers much better performance where you need to transfer frames to SPI-connected displays for instance.
 
Similar to ‘writebytes‘ but accepts arbitrary large lists. If list size exceeds buffer size (which is read from <code>/sys/module/spidev/parameters/bufsiz</code>), data will be split into smaller chunks and sent in multiple operations. Also, writebytes2 understands [https://docs.python.org/3/c-api/buffer.html buffer protocol] so it can accept numpy byte arrays for example without need to convert them with <code>tolist()</code> first. This offers much better performance where you need to transfer frames to SPI-connected displays for instance.
* <code>xfer(list of values[, speed_hz, delay_usec, bits_per_word])</code><br />
+
</li>
 +
<li><code>xfer(list of values[, speed_hz, delay_usec, bits_per_word])</code><br />
 
Performs an SPI transaction. Chip-select should be released and reactivated between blocks. Delay specifies the delay in usec between blocks.
 
Performs an SPI transaction. Chip-select should be released and reactivated between blocks. Delay specifies the delay in usec between blocks.
* <code>xfer2(list of values[, speed_hz, delay_usec, bits_per_word])</code><br />
+
</li>
 +
<li><code>xfer2(list of values[, speed_hz, delay_usec, bits_per_word])</code><br />
 
Performs an SPI transaction. Chip-select should be held active between blocks.
 
Performs an SPI transaction. Chip-select should be held active between blocks.
* <code>xfer3(list of values[, speed_hz, delay_usec, bits_per_word])</code><br />
+
</li>
 +
<li><code>xfer3(list of values[, speed_hz, delay_usec, bits_per_word])</code><br />
 
Similar to xfer2 but accepts arbitrary large lists. If list size exceeds buffer size (which is read from <code>/sys/module/spidev/parameters/bufsiz</code>), data will be split into smaller chunks and sent in multiple operations.
 
Similar to xfer2 but accepts arbitrary large lists. If list size exceeds buffer size (which is read from <code>/sys/module/spidev/parameters/bufsiz</code>), data will be split into smaller chunks and sent in multiple operations.
* <code>close()</code><br />
+
</li>
 +
<li><code>close()</code><br />
 
Disconnects from the SPI device.
 
Disconnects from the SPI device.
 +
</li>
 +
</ul>
  
== Using Threads ==
+
= Using Threads =
  
 
[https://realpython.com/intro-to-python-threading/ This guide] serves as the basis for the text below. This manual does not teach everything there is to know about threading in Python, but will give you the basics to be able to utilize threads for simple tasks that may be of use to you in the practicals. It’s strongly recommended you read through that text if you have not yet been exposed to threading concepts.
 
[https://realpython.com/intro-to-python-threading/ This guide] serves as the basis for the text below. This manual does not teach everything there is to know about threading in Python, but will give you the basics to be able to utilize threads for simple tasks that may be of use to you in the practicals. It’s strongly recommended you read through that text if you have not yet been exposed to threading concepts.
Line 224: Line 241:
 
This text does not cover issues and precautions when working with threads, such as mutex’s and locks, data races, producer/consumer concerns and the likes. However, these are knowledge areas of critical importance, and it is strongly suggested that the reader tries to make an effort to understand these concerns before writing threaded code.
 
This text does not cover issues and precautions when working with threads, such as mutex’s and locks, data races, producer/consumer concerns and the likes. However, these are knowledge areas of critical importance, and it is strongly suggested that the reader tries to make an effort to understand these concerns before writing threaded code.
  
=== The Threading Library ===
+
== The Threading Library ==
  
 
The Python library <code>threading</code> library offers all the functionality one would expect. Import it in to your code in the standard way:
 
The Python library <code>threading</code> library offers all the functionality one would expect. Import it in to your code in the standard way:
Line 236: Line 253:
 
# “Shut it down” by joining
 
# “Shut it down” by joining
  
=== Basic Thread Usage ===
+
== Basic Thread Usage ==
  
 
For example, if you want to create a separate thread to fetch a sensor value five times, you could do it as follows:
 
For example, if you want to create a separate thread to fetch a sensor value five times, you could do it as follows:
  
<pre>import threading
+
<syntaxhighlight lang="python" line='line'>import threading
 
import time
 
import time
 
import RPi.GPIO as GPIO
 
import RPi.GPIO as GPIO
Line 252: Line 269:
 
         time.sleep(2)
 
         time.sleep(2)
 
      
 
      
if __name__ == &quot;__main__&quot;:
+
if __name__ == "__main__":
 
     setup()
 
     setup()
     # Create a thread to call the function and pass &quot;12&quot; in as sensor pin
+
     # Create a thread to call the function and pass "12" in as sensor pin
 
     x = threading.Thread(target=fetch_sensor_vals, args=(12,))  
 
     x = threading.Thread(target=fetch_sensor_vals, args=(12,))  
     print(&quot;Starting thread&quot;)
+
     print("Starting thread")
 
     x.start()
 
     x.start()
     print(&quot;Waiting for the thread to finish&quot;)
+
     print("Waiting for the thread to finish")
 
     x.join()
 
     x.join()
     print(&quot;Reading finished&quot;)</pre>
+
     print("Reading finished")
=== Timed Thread Usage ===
+
</syntaxhighlight>
 +
 
 +
== Timed Thread Usage ==
  
 
Often in embedded systems we want a specific task to run every X time units. There is an option in the Python threading library. It works as follows:
 
Often in embedded systems we want a specific task to run every X time units. There is an option in the Python threading library. It works as follows:
  
<pre>import threading
+
<syntaxhighlight lang="python" line='line'>import threading
 
import datetime
 
import datetime
  
&quot;&quot;&quot;
+
def print_time_thread():
This function prints the time to the screen every five seconds
+
    """
&quot;&quot;&quot;
+
    This function prints the time to the screen every five seconds
def print_time():
+
    """
     YOUR = threading.Timer(5.0, print_time)
+
     thread = threading.Timer(5.0, print_time_thread)
     YOUR.daemon = True
+
     thread.daemon = True # Daemon threads exit when the program does
     YOUR.start()
+
     thread.start()
 
     print(datetime.datetime.now())
 
     print(datetime.datetime.now())
 
      
 
      
  
if __name__ == &quot;__main__&quot;:
+
if __name__ == "__main__":
     print_time() # call it once to start the thread
+
     print_time_thread() # call it once to start the thread
 
      
 
      
 
     # Tell our program to run indefinitely
 
     # Tell our program to run indefinitely
 
     while True:
 
     while True:
         pass</pre>
+
         pass
 +
 
 +
</syntaxhighlight>
 +
 
 
You will see that in this code example, we have also set the <code>daemon</code> flag of the thread to be be <code>True</code>. A daemon thread will shut down immediately when the program exits. In essence, the daemon thread will run indefinitely in the background until your application exits, and you do not need to worry about calling <code>join()</code>.<ref>It’s important to note that calling t.join() will wait for a thread to finish executing, even if it is a daemon.
 
You will see that in this code example, we have also set the <code>daemon</code> flag of the thread to be be <code>True</code>. A daemon thread will shut down immediately when the program exits. In essence, the daemon thread will run indefinitely in the background until your application exits, and you do not need to worry about calling <code>join()</code>.<ref>It’s important to note that calling t.join() will wait for a thread to finish executing, even if it is a daemon.
 
</ref>
 
</ref>
 
= Programming in C/C++ =
 

Latest revision as of 06:03, 28 October 2020

Overview[edit]

Most of the content in the guide comes from the documentation, available online here.

Python, while not as powerful as C or C++, is quickly becoming a common choice for embedded systems developers due to its ease of use and rapid development times. See this IEEE article. For information on programming in C or C++, go to RaspberryPi:ProgrammingInC.

This chapter serves as a short guide for programming in Python on the RPi. You will also find some templates for techniques such as debouncing, or making use of the Raspberry Pi’s multicore architecture by implementing threading.

The RPI.GPIO Library[edit]

The RPi.GPIO library is the common Python library used on the Raspberry Pi. Documentation for the library can be found here:
https://sourceforge.net/p/raspberry-gpio-python/wiki/Home/

It is included in the environment variables by default, so to use it, you can simply just import it into your Python script:

import RPi.GPIO as GPIO

You need to specify which board mode you’re using. For more information on board modes, see Section RaspberryPi:Overview#GPIO_Pins. Board modes are specified as follows:

GPIO.setmode(GPIO.BOARD)
# or
GPIO.setmode(GPIO.BCM)

You also need to perform “cleanup” on GPIOs when your application exists. By performing cleanup and resetting all pins to their default modes, you can prevent possible damage to your Raspberry Pi. The cleanup function is called as follows:

GPIO.cleanup()

If your program is meant to run indefinitely, and only close upon an exception, you can wrap it in a try catch, as follows:

if __name__ == "__main__":
    # Make sure the GPIO is stopped correctly
    try:
        while True:
            main()
    except KeyboardInterrupt:
        print("Exiting gracefully")
        GPIO.cleanup()
    except e:
        print("Some other error occurred: {}".format(e.message)})
        GPIO.cleanup()

Basic IO[edit]

Read the documentation, available here.

Digital Logic[edit]

Digital output on the Raspberry Pi is accomplished by writing values to channels. A channel is a pin, which is numbered in the way you’ve configured. Basic configuration of a pin for output is as follows:

GPIO.output(<channel>, <Logic>)

Your logic (high, 3.3V, or low, 0V), can be specified as follows:

  • For 3.3V (high) output:

    GPIO.output(<channel>, GPIO.HIGH)
    GPIO.output(<channel>, 1)
    GPIO.output(<channel>, True)
    
  • For 0V (low) output:

    GPIO.output(<channel>, GPIO.LOW)
    GPIO.output(<channel>, 0)
    GPIO.output(<channel>, False)
    

Channels can also be specified in lists, for example:

LEDs = (11,12)
GPIO.output(LEDs, GPIO.HIGH) # Will turn all channels HIGH
GPIO.output(LEDs, (GPIO.HIGH, GPIO.LOW)) # Will the first channel HIGH, and the second LOW

Note that you can also read the state of the pin/channel that is set as an output by using the input() function. For example, if you wanted to toggle pin 12, you could do something as follows:

GPIO.output(12, not GPIO.input(12))

Analog[edit]

The Raspberry Pi does not have any analog input pins. You will need to use something like the MCP3008 to read analog voltages.

Inputs[edit]

Digital Read[edit]

To read the value of a digital pin, you can use the input() function:

if GPIO.input(12):
    print("Pin 12 HIGH")
else:
    print("Pin 12 LOW")

Pull Up/Pull Down Resistors[edit]

Using pull up and pull down resistors is essential when working with digital logic. Thankfully, the Raspberry Pi has internal pull up and pull down resistors. To make use of these, initialize the pin/channel as follows:

GPIO.setup(<channel>, GPIO.IN, pull_up_down=GPIO.PUD_UP)
# or
GPIO.setup(<channel>, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)

Interrupts[edit]

Very often it is useful to set up an interrupt, for example to trigger an event when a button is pressed. Callback functions are executed on a different thread. If you have multiple callbacks, know that they will be executed sequentially as only one thread exists for interrupts. Interrupts can be implemented as follows:

# The bounce time is given in milliseconds.
# If your pin is set to use pull down resistors
# Connect a button between <channel> and 3.3V
GPIO.add_event_detect(<channel>, GPIO.RISING, callback=callback_method(), bouncetime=200)
# If your pin is set to use pull up resistors
# Connect a button between <channel> and GND
GPIO.add_event_detect(<channel>, GPIO.FALLING, callback=callback_method(), bouncetime=200)

Other functions[edit]

The RPi.GPIO Library offers other functions for interrupts the first is the wait_for_edge() function, which is designed to block execution until an edge is detected. You can detect edges of GPIO.RISING, GPIO.FALLING, or GPIO.BOTH. The timeout is given in milliseconds:

# wait for up to 5 seconds for a rising edge
edge = GPIO.wait_for_edge(<channel>, GPIO_RISING, timeout=5000)
if edge is None:
    print('Timeout occurred')
else:
    print('Edge detected on pin <CHANNEL>')

Another function is the event_detected() function. From the docs: “The event_detected() function is designed to be used in a loop with other things, but unlike polling it is not going to miss the change in state of an input while the CPU is busy working on other things. This could be useful when using something like Pygame or PyQt where there is a main loop listening and responding to GUI events in a timely basis.”

GPIO.add_event_detect(channel, GPIO.RISING)  # add rising edge detection on a channel
if GPIO.event_detected(channel):
    print('Button pressed')

Communication Protocols[edit]

The RPi.GPIO library has no native support for communication protocols. Very often, specific Python libraries are provided on a per-device use case. However, this can become tedious and cause your code to become bloated. See the sections below for relevant libraries for using I2C and SPI directly.

I2C[edit]

Ensure you enable I2C in raspi-config.

To use I2C in Python, you can use the smbus library. To do this, we need to install and configure smbus. This is usually done by default, but instructions are included for posterity.

$ sudo apt-get install i2c-tools
$ sudo apt-get install python-smbus
$ sudo adduser <username> i2c
$ sudo reboot

Once you connect a device, you can run $gpio i2cdetect to determine if a device is detected on the I2C bus. In the example below, there is an electronic compass (GY-271) connected to the Pi.

An i2cdetect example

To initialize an I2C device, do the following:

import smbus
i2cdevice = smbus.SMBus(1) # 1 indicates /dev/i2c-1
address = 0x1e #whatever the device is for your i2c device

To read a byte from the I2C device:

result = i2cdevice.read_byte_data(address, <register>)

To write a byte to the I2C device:

i2cdevice.write_byte_data(address, <register>, <value>)

Methods Available[edit]

smbus has multiple methods. The documentation for these is quite minimal, but brief descriptions are available here.

Note: If you're working with EEPROM (specifically 24C32), you may need to read the answer to the questions given here.

SPI[edit]

Ensure you enable SPI in raspi-config.

You can use SPI in Python by using the spidev library. Documentation is available here. Basic usage is as follows:

import spidev

#Bus is 0 or 1, depending on which SPI bus you've connected to
bus = 0
#Device is the chip select pin. Set to 0 or 1, depending on the connections
device = 1

spi = spidev.SpiDev() #Enable SPI
spi.open(bus, device) #Open connection to a specific bus and device (CS pin)

# Set settings (SPI speed and mode)
spi.max_speed_hz = 500000
spi.mode = 0

to_send = [0x01, 0x02, 0x03] #define what to send
spi.xfer(to_send)

# Close the SPI connection
close()

Settings[edit]

The following settings are configurable in the spidev library, and can be set as follows:

spi = spidev.SpiDev() #Enable SPI
spi.<setting> = <value>

List of settings:

  • bits_per_word
  • cs_high
  • loop

Set the “SPI_LOOP” flag to enable loopback mode

  • no_cs

Set the “SPI_NO_CS” flag to disable use of the chip select (although the driver may still own the CS pin)

  • lsbfirst
  • max_speed_hz
  • mode

SPI mode as two bit pattern of clock polarity and phase [CPOL/CPHA], min: 0b00 = 0, max: 0b11 = 3

  • threewire

SI/SO signals shared

Methods[edit]

The following methods are available in the spidev library

  • open(bus, device)
    Connects to the specified SPI device, opening /dev/spidev<bus>.<device>
  • readbytes(n)
    Read n bytes from SPI device.
  • writebytes(list of values)
    Writes a list of values to SPI device.
  • writebytes2(list of values)
    Similar to ‘writebytes‘ but accepts arbitrary large lists. If list size exceeds buffer size (which is read from /sys/module/spidev/parameters/bufsiz), data will be split into smaller chunks and sent in multiple operations. Also, writebytes2 understands buffer protocol so it can accept numpy byte arrays for example without need to convert them with tolist() first. This offers much better performance where you need to transfer frames to SPI-connected displays for instance.
  • xfer(list of values[, speed_hz, delay_usec, bits_per_word])
    Performs an SPI transaction. Chip-select should be released and reactivated between blocks. Delay specifies the delay in usec between blocks.
  • xfer2(list of values[, speed_hz, delay_usec, bits_per_word])
    Performs an SPI transaction. Chip-select should be held active between blocks.
  • xfer3(list of values[, speed_hz, delay_usec, bits_per_word])
    Similar to xfer2 but accepts arbitrary large lists. If list size exceeds buffer size (which is read from /sys/module/spidev/parameters/bufsiz), data will be split into smaller chunks and sent in multiple operations.
  • close()
    Disconnects from the SPI device.

Using Threads[edit]

This guide serves as the basis for the text below. This manual does not teach everything there is to know about threading in Python, but will give you the basics to be able to utilize threads for simple tasks that may be of use to you in the practicals. It’s strongly recommended you read through that text if you have not yet been exposed to threading concepts.

This text does not cover issues and precautions when working with threads, such as mutex’s and locks, data races, producer/consumer concerns and the likes. However, these are knowledge areas of critical importance, and it is strongly suggested that the reader tries to make an effort to understand these concerns before writing threaded code.

The Threading Library[edit]

The Python library threading library offers all the functionality one would expect. Import it in to your code in the standard way:

import threading

There are 4 basic things you need to do when creating a thread:

  1. Initialize it
  2. Start it
  3. Let is execute
  4. “Shut it down” by joining

Basic Thread Usage[edit]

For example, if you want to create a separate thread to fetch a sensor value five times, you could do it as follows:

 1 import threading
 2 import time
 3 import RPi.GPIO as GPIO
 4 
 5 def setup():
 6     #Contains all code for initialisation of RPi.GPIO
 7 
 8 def fetch_sensor_vals(sensor_pin):
 9     for i in range(5):
10         GPIO.input(sensor_pin)
11         time.sleep(2)
12     
13 if __name__ == "__main__":
14     setup()
15     # Create a thread to call the function and pass "12" in as sensor pin
16     x = threading.Thread(target=fetch_sensor_vals, args=(12,)) 
17     print("Starting thread")
18     x.start()
19     print("Waiting for the thread to finish")
20     x.join()
21     print("Reading finished")

Timed Thread Usage[edit]

Often in embedded systems we want a specific task to run every X time units. There is an option in the Python threading library. It works as follows:

 1 import threading
 2 import datetime
 3 
 4 def print_time_thread():
 5     """
 6     This function prints the time to the screen every five seconds
 7     """
 8     thread = threading.Timer(5.0, print_time_thread)
 9     thread.daemon = True  # Daemon threads exit when the program does
10     thread.start()
11     print(datetime.datetime.now())
12     
13 
14 if __name__ == "__main__":
15     print_time_thread() # call it once to start the thread
16     
17     # Tell our program to run indefinitely
18     while True:
19         pass

You will see that in this code example, we have also set the daemon flag of the thread to be be True. A daemon thread will shut down immediately when the program exits. In essence, the daemon thread will run indefinitely in the background until your application exits, and you do not need to worry about calling join().<ref>It’s important to note that calling t.join() will wait for a thread to finish executing, even if it is a daemon. </ref>