37 Commits
v0.0.3 ... main

Author SHA1 Message Date
Phil Howard
20d6e8058f install.sh: sync with boilerplate.
Fix auto_venv.sh creation and path.
Normalise some whitespace.
Better handling for /boot/firmware/config.txt.
2023-11-22 11:04:06 +00:00
Philip Howard
432a58fea9 Merge pull request #34 from pimoroni/patch-v1.0.0-prep
Tidyup, bugfixes and prep for v1.0.0
2023-11-15 10:28:15 +00:00
Phil Howard
ed2b0af910 Tidyup, bugfixes and prep for v1.0.0 2023-11-15 10:20:44 +00:00
Philip Howard
1a07965d2d Merge pull request #33 from pimoroni/repackage
Repackage to pyproject/hatch and port to gpiod
2023-11-08 13:38:45 +00:00
Phil Howard
c9f6459ee6 Update README 2023-11-08 09:47:39 +00:00
Phil Howard
2b1736c669 Port to gpiod/gpiodevice. 2023-11-08 09:30:36 +00:00
Phil Howard
8e6cee4ef8 Port to gpiod/gpiodevice. 2023-11-08 09:19:00 +00:00
Phil Howard
47fc5e1c78 CI: Fix tests for st7735 rename. 2023-11-03 13:49:21 +00:00
Phil Howard
147ccfcc11 QA: Apply isort suggestions. 2023-11-03 13:43:19 +00:00
Phil Howard
f6599b1854 Repackage to latest Python boilerplate.
Deprecate "ST7735" and rename module to "st7735".
2023-11-03 13:42:34 +00:00
Phil Howard
c0733d678a Prep for v0.0.5 2023-08-07 11:35:02 +01:00
Philip Howard
8d4235b9b3 Merge pull request #24 from lynniemagoo/master
Issue #22 - Cannot display proper color on Adafruit 358 160x128 ST7735R TFT display
2023-08-07 10:11:47 +01:00
Philip Howard
89b4406492 Merge pull request #26 from nickbroon/patch-1
Add py3 debian dependencies
2023-08-07 10:08:18 +01:00
Philip Howard
b1e35beb4b Merge pull request #31 from tomjn/sleep_wake_on_off
Sleep wake on off
2023-08-07 10:06:46 +01:00
Tom J Nowell
f94e1aa318 adds sleep/wake/display on and off methods 2023-07-27 10:53:20 +01:00
Philip Howard
94f8bc2423 Merge pull request #30 from pimoroni/patch-ci-python-versions
CI: Bump Python versions, fix tests.
2023-07-26 15:54:42 +01:00
Phil Howard
6719ad8dac CI: Bump Python versions, fix tests. 2023-07-26 15:31:33 +01:00
Nick Brown
cf5c2b3df4 Add py3 debian dependencies
These python modules are directly used from this library so dependencies should be expressed.
Might be worth consider adding them to `install_requires` as well.
2022-07-01 16:56:44 +01:00
lynniemagoo
3b68b77b29 Issue #22 - Remove debug print statements. 2022-03-17 18:22:50 -05:00
lynniemagoo
99e44092de Issue #22 - Provide support to invert MADCTL from BGR to RGB for Adafruit #358 display https://www.adafruit.com/product/358 2022-03-17 18:21:14 -05:00
Philip Howard
1a70d3ae7e Merge pull request #18 from pimoroni/patch-ci
Tweak CI Python versions
2021-09-15 15:14:06 +01:00
Phil Howard
1d8d0faf1b Tweak CI Python versions 2021-09-15 12:34:01 +01:00
Philip Howard
d2d1e96f62 Prep for v0.0.4-post1
Ship new setup.cfg/README.md packaging style and fixed __version__ in __init__.py
2021-09-06 13:48:42 +01:00
Phil Howard
bd02b98395 Update README install instructions for #14 2021-04-12 17:20:04 +01:00
Philip Howard
f364ee3455 Merge pull request #15 from pimoroni/update-packaging
Update packaging
2021-04-12 17:03:24 +01:00
Phil Howard
f4c3190ba6 Update packaging 2021-04-12 16:52:08 +01:00
Philip Howard
9731c4257d Merge pull request #11 from pimoroni/actions
Add GitHub actions workflow
2020-11-14 15:12:47 +00:00
Phil Howard
8315a3daa6 Remove .travis.yml 2020-11-14 15:11:33 +00:00
Phil Howard
f4a234a871 Add GitHub actions workflow 2020-11-14 15:04:28 +00:00
Philip Howard
7ceb945c9a Merge pull request #10 from pimoroni/improve-tests
Improve test quality and coverage
2020-03-20 12:08:38 +00:00
Phil Howard
6f94d98040 Make stickler happy 2020-03-20 12:00:31 +00:00
Phil Howard
f532c80fbf Improve test quality and coverage 2020-03-17 16:10:41 +00:00
Phil Howard
86739c6f4c Prep for v0.0.4 2020-03-13 15:01:41 +00:00
Philip Howard
4f0d537021 Merge pull request #7 from pimoroni/spi-segfault-fix
SPI segfault fix
2020-02-05 14:02:00 +00:00
Phil Howard
3009ef94c3 Hopeful fix for spidev segfault issue #6
From continued testing up to 1.8 million simulated display updates it appears that either updating to spidev==3.4 or using xfer3 fixes the issue with a segfault after ~167000 updates.
2020-01-03 17:48:54 +00:00
nkotilainen
640596059d Update gif.py 2019-05-22 13:10:48 +01:00
Phil Howard
85c91e05e3 Updated examples to use front backlight pin 2019-04-30 12:48:06 +01:00
36 changed files with 1075 additions and 380 deletions

View File

@@ -19,7 +19,7 @@ common troubleshooting steps below before creating the issue:
library the code depends on is not installed. Check the tutorial/guide or
README to ensure you have installed the necessary libraries. Usually the
missing library can be installed with the `pip` tool, but check the tutorial/guide
for the exact command.
for the exact command.
- **Be sure you are supplying adequate power to the board.** Check the specs of
your board and power in an external power supply. In many cases just

41
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,41 @@
name: Build
on:
pull_request:
push:
branches:
- main
jobs:
test:
name: Python ${{ matrix.python }}
runs-on: ubuntu-latest
strategy:
matrix:
python: ['3.9', '3.10', '3.11']
env:
RELEASE_FILE: ${{ github.event.repository.name }}-${{ github.event.release.tag_name || github.sha }}-py${{ matrix.python }}
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python }}
- name: Install Dependencies
run: |
make dev-deps
- name: Build Packages
run: |
make build
- name: Upload Packages
uses: actions/upload-artifact@v3
with:
name: ${{ env.RELEASE_FILE }}
path: dist/

36
.github/workflows/qa.yml vendored Normal file
View File

@@ -0,0 +1,36 @@
name: QA
on:
pull_request:
push:
branches:
- main
jobs:
test:
name: linting & spelling
runs-on: ubuntu-latest
env:
TERM: xterm-256color
steps:
- name: Checkout Code
uses: actions/checkout@v2
- name: Set up Python '3,11'
uses: actions/setup-python@v3
with:
python-version: '3.11'
- name: Install Dependencies
run: |
make dev-deps
- name: Run Quality Assurance
run: |
make qa
- name: Run Code Checks
run: |
make check

41
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,41 @@
name: Tests
on:
pull_request:
push:
branches:
- main
jobs:
test:
name: Python ${{ matrix.python }}
runs-on: ubuntu-latest
strategy:
matrix:
python: ['3.9', '3.10', '3.11']
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python }}
- name: Install Dependencies
run: |
make dev-deps
- name: Run Tests
run: |
make pytest
- name: Coverage
if: ${{ matrix.python == '3.9' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
python -m pip install coveralls
coveralls --service=github

View File

@@ -1,24 +0,0 @@
language: python
sudo: false
cache: pip
git:
submodules: true
matrix:
include:
- python: "2.7"
env: TOXENV=py27
- python: "3.5"
env: TOXENV=py35
- python: "2.7"
env: TOXENV=py27
install:
- pip install --ignore-installed --upgrade setuptools pip tox coveralls
script:
- cd library
- tox -vv
after_success: if [ "$TOXENV" == "py35" ]; then coveralls; fi

43
CHANGELOG.md Normal file
View File

@@ -0,0 +1,43 @@
1.0.0
-----
* Rename module from ST7735 to st7735
* Port to gpiod/gpiodevice
0.0.5
-----
* Add support for choosing between BGR/RGB displays
* Add methods for display power and sleep control
0.0.4-post1
-----------
* Repackage with Markdown README/setup.cfg
* Fix `__version__` to 0.0.4
* Update dependencies in README
0.0.4
-----
* Depend upon spidev==3.4.0 for stability fixes
* Switch from manual data chunking to spidev.xfer3()
0.0.3
-----
* Fixed backlight pin
* Added `set_backlight`
* Added constants BG_SPI_CS_FRONT and BG_SPI_CS_BACK
* Added module `__version__`
0.0.2
-----
* Support for multiple display sizes/orientations
0.0.1
-----
* Initial Release

View File

@@ -1,47 +1,60 @@
.PHONY: usage install uninstall
LIBRARY_NAME := $(shell hatch project metadata name 2> /dev/null)
LIBRARY_VERSION := $(shell hatch version 2> /dev/null)
.PHONY: usage install uninstall check pytest qa build-deps check tag wheel sdist clean dist testdeploy deploy
usage:
ifdef LIBRARY_NAME
@echo "Library: ${LIBRARY_NAME}"
@echo "Version: ${LIBRARY_VERSION}\n"
else
@echo "WARNING: You should 'make dev-deps'\n"
endif
@echo "Usage: make <target>, where target is one of:\n"
@echo "install: install the library locally from source"
@echo "uninstall: uninstall the local library"
@echo "python-readme: generate library/README.rst from README.md"
@echo "python-wheels: build python .whl files for distribution"
@echo "python-sdist: build python source distribution"
@echo "python-clean: clean python build and dist directories"
@echo "python-dist: build all python distribution files"
@echo "install: install the library locally from source"
@echo "uninstall: uninstall the local library"
@echo "dev-deps: install Python dev dependencies"
@echo "check: perform basic integrity checks on the codebase"
@echo "qa: run linting and package QA"
@echo "pytest: run Python test fixtures"
@echo "clean: clean Python build and dist directories"
@echo "build: build Python distribution files"
@echo "testdeploy: build and upload to test PyPi"
@echo "deploy: build and upload to PyPi"
@echo "tag: tag the repository with the current version\n"
install:
./install.sh
./install.sh --unstable
uninstall:
./uninstall.sh
python-readme: library/README.rst
dev-deps:
python3 -m pip install -r requirements-dev.txt
sudo apt install dos2unix
python-license: library/LICENSE.txt
check:
@bash check.sh
library/README.rst: README.md
pandoc --from=markdown --to=rst -o library/README.rst README.md
qa:
tox -e qa
library/LICENSE.txt: LICENSE
cp LICENSE library/LICENSE.txt
pytest:
tox -e py
python-wheels: python-readme python-license
cd library; python3 setup.py bdist_wheel
cd library; python setup.py bdist_wheel
nopost:
@bash check.sh --nopost
python-sdist: python-readme python-license
cd library; python setup.py sdist
tag:
git tag -a "v${LIBRARY_VERSION}" -m "Version ${LIBRARY_VERSION}"
python-clean:
-rm -r library/dist
-rm -r library/build
-rm -r library/*.egg-info
build: check
@hatch build
python-dist: python-clean python-wheels python-sdist
ls library/dist
clean:
-rm -r dist
python-deploy: python-dist
twine upload library/dist/*
testdeploy: build
twine upload --repository testpypi dist/*
python-deploy-test: python-dist
twine upload --repository-url https://test.pypi.org/legacy/ library/dist/*
deploy: nopost build
twine upload dist/*

View File

@@ -1,31 +1,22 @@
# Python ST7735
[![Build Status](https://travis-ci.com/pimoroni/st7735-python.svg?branch=master)](https://travis-ci.com/pimoroni/st7735-python)
[![Build Status](https://img.shields.io/github/actions/workflow/status/pimoroni/st7735-python/test.yml?branch=main)](https://github.com/pimoroni/st7735-python/actions/workflows/test.yml)
[![Coverage Status](https://coveralls.io/repos/github/pimoroni/st7735-python/badge.svg?branch=master)](https://coveralls.io/github/pimoroni/st7735-python?branch=master)
[![PyPi Package](https://img.shields.io/pypi/v/st7735.svg)](https://pypi.python.org/pypi/st7735)
[![Python Versions](https://img.shields.io/pypi/pyversions/st7735.svg)](https://pypi.python.org/pypi/st7735)
Python library to control an ST7735 TFT LCD display. Allows simple drawing on the display without installing a kernel module.
Designed specifically to work with a ST7735 based 160x80 pixel TFT SPI display. (Specifically the 0.96" SPI LCD from Pimoroni).
Make sure you have the following dependencies:
## Installing
````
sudo apt-get update
sudo apt-get install python-rpi.gpio python-spidev python-pip python-imaging python-numpy
````
Install this library by running:
````
sudo pip install st7735
pip install st7735
````
See example of usage in the examples folder.
# Licensing & History
This library is a modification of a modification of code originally written by Tony DiCola for Adafruit Industries, and modified to work with the ST7735 by Clement Skau.

5
ST7735.py Normal file
View File

@@ -0,0 +1,5 @@
from warnings import warn
from st7735 import * # noqa F403
warn("Using \"import ST7735\" is deprecated. Please \"import st7735\" (all lowercase)!", DeprecationWarning, stacklevel=2)

87
check.sh Executable file
View File

@@ -0,0 +1,87 @@
#!/bin/bash
# This script handles some basic QA checks on the source
NOPOST=$1
LIBRARY_NAME=`hatch project metadata name`
LIBRARY_VERSION=`hatch version | awk -F "." '{print $1"."$2"."$3}'`
POST_VERSION=`hatch version | awk -F "." '{print substr($4,0,length($4))}'`
success() {
echo -e "$(tput setaf 2)$1$(tput sgr0)"
}
inform() {
echo -e "$(tput setaf 6)$1$(tput sgr0)"
}
warning() {
echo -e "$(tput setaf 1)$1$(tput sgr0)"
}
while [[ $# -gt 0 ]]; do
K="$1"
case $K in
-p|--nopost)
NOPOST=true
shift
;;
*)
if [[ $1 == -* ]]; then
printf "Unrecognised option: $1\n";
exit 1
fi
POSITIONAL_ARGS+=("$1")
shift
esac
done
inform "Checking $LIBRARY_NAME $LIBRARY_VERSION\n"
inform "Checking for trailing whitespace..."
grep -IUrn --color "[[:blank:]]$" --exclude-dir=dist --exclude-dir=.tox --exclude-dir=.git --exclude=PKG-INFO
if [[ $? -eq 0 ]]; then
warning "Trailing whitespace found!"
exit 1
else
success "No trailing whitespace found."
fi
printf "\n"
inform "Checking for DOS line-endings..."
grep -lIUrn --color $'\r' --exclude-dir=dist --exclude-dir=.tox --exclude-dir=.git --exclude=Makefile
if [[ $? -eq 0 ]]; then
warning "DOS line-endings found!"
exit 1
else
success "No DOS line-endings found."
fi
printf "\n"
inform "Checking CHANGELOG.md..."
cat CHANGELOG.md | grep ^${LIBRARY_VERSION} > /dev/null 2>&1
if [[ $? -eq 1 ]]; then
warning "Changes missing for version ${LIBRARY_VERSION}! Please update CHANGELOG.md."
exit 1
else
success "Changes found for version ${LIBRARY_VERSION}."
fi
printf "\n"
inform "Checking for git tag ${LIBRARY_VERSION}..."
git tag -l | grep -E "${LIBRARY_VERSION}$"
if [[ $? -eq 1 ]]; then
warning "Missing git tag for version ${LIBRARY_VERSION}"
fi
printf "\n"
if [[ $NOPOST ]]; then
inform "Checking for .postN on library version..."
if [[ "$POST_VERSION" != "" ]]; then
warning "Found .$POST_VERSION on library version."
inform "Please only use these for testpypi releases."
exit 1
else
success "OK"
fi
fi

View File

@@ -18,36 +18,36 @@
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
import time
import math
import sys
import time
from PIL import Image
from PIL import ImageDraw
import ST7735 as ST7735
from PIL import Image, ImageDraw
SPI_SPEED_MHZ = 10 # Higher speed = higher framerate
import st7735
SPI_SPEED_MHZ = 4 # Higher speed = higher framerate
if len(sys.argv) > 1:
SPI_SPEED_MHZ = int(sys.argv[1])
print("""
print(f"""
framerate.py - Test LCD framerate.
If you're using Breakout Garden, plug the 0.96" LCD (SPI)
breakout into the rear slot.
Running at: {}MHz
""".format(SPI_SPEED_MHZ))
Running at: {SPI_SPEED_MHZ}MHz
""")
# Create ST7735 LCD display class.
disp = ST7735.ST7735(
disp = st7735.ST7735(
port=0,
cs=ST7735.BG_SPI_CS_FRONT,
dc=9,
backlight=18,
cs=st7735.BG_SPI_CS_FRONT, # BG_SPI_CS_BACK or BG_SPI_CS_FRONT. BG_SPI_CS_FRONT (eg: CE1) for Enviro Plus
dc="PIN21", # "GPIO9" / "PIN21". "PIN21" for a Pi 5 with Enviro Plus
backlight="PIN32", # "PIN18" for back BG slot, "PIN19" for front BG slot. "PIN32" for a Pi 5 with Enviro Plus
rotation=90,
spi_speed_hz=SPI_SPEED_MHZ * 1000000
spi_speed_hz=4000000
)
WIDTH = disp.width
@@ -78,7 +78,4 @@ while True:
count += 1
time_current = time.time() - time_start
if count % 120 == 0:
print("Time: {:8.3f}, Frames: {:6d}, FPS: {:8.3f}".format(
time_current,
count,
count / time_current))
print(f"Time: {time_current:8.3f}, Frames: {count:6d}, FPS: {count / time_current:8.3f}")

View File

@@ -18,30 +18,33 @@
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
from PIL import Image
import ST7735
import time
import sys
import time
from PIL import Image
import st7735
print("""
gif.py - Display a gif on the LCD.
If you're using Breakout Garden, plug the 0.96" LCD (SPI)
breakout into the rear slot.
breakout into the front slot.
""")
if len(sys.argv) > 1:
image_file = sys.argv[1]
else:
print("Usage: {} <filename.gif>".format(sys.argv[0]))
print(f"Usage: {sys.argv[0]} <filename.gif>")
sys.exit(0)
# Create TFT LCD display class.
disp = ST7735.ST7735(
disp = st7735.ST7735(
port=0,
cs=ST7735.BG_SPI_CS_FRONT,
dc=9,
backlight=18,
cs=st7735.BG_SPI_CS_FRONT, # BG_SPI_CS_BACK or BG_SPI_CS_FRONT. BG_SPI_CS_FRONT (eg: CE1) for Enviro Plus
dc="PIN21", # "GPIO9" / "PIN21". "PIN21" for a Pi 5 with Enviro Plus
backlight="PIN32", # "PIN18" for back BG slot, "PIN19" for front BG slot. "PIN32" for a Pi 5 with Enviro Plus
rotation=90,
spi_speed_hz=4000000
)
@@ -52,10 +55,10 @@ width = disp.width
height = disp.height
# Load an image.
print('Loading gif: {}...'.format(image_file))
print(f"Loading gif: {image_file}...")
image = Image.open(image_file)
print('Drawing gif, press Ctrl+C to exit!')
print("Drawing gif, press Ctrl+C to exit!")
frame = 0

View File

@@ -21,7 +21,8 @@
import sys
from PIL import Image
import ST7735 as ST7735
import st7735
print("""
image.py - Display an image on the LCD.
@@ -31,17 +32,17 @@ breakout into the rear slot.
""")
if len(sys.argv) < 2:
print("Usage: {} <image_file>".format(sys.argv[0]))
print(f"Usage: {sys.argv[0]} <image_file>")
sys.exit(1)
image_file = sys.argv[1]
# Create ST7735 LCD display class.
disp = ST7735.ST7735(
disp = st7735.ST7735(
port=0,
cs=ST7735.BG_SPI_CS_FRONT,
dc=9,
backlight=18,
cs=st7735.BG_SPI_CS_FRONT, # BG_SPI_CS_BACK or BG_SPI_CS_FRONT. BG_SPI_CS_FRONT (eg: CE1) for Enviro Plus
dc="PIN21", # "GPIO9" / "PIN21". "PIN21" for a Pi 5 with Enviro Plus
backlight="PIN32", # "PIN18" for back BG slot, "PIN19" for front BG slot. "PIN32" for a Pi 5 with Enviro Plus
rotation=90,
spi_speed_hz=4000000
)
@@ -53,13 +54,13 @@ HEIGHT = disp.height
disp.begin()
# Load an image.
print('Loading image: {}...'.format(image_file))
print(f"Loading image: {image_file}...")
image = Image.open(image_file)
# Resize the image
image = image.resize((WIDTH, HEIGHT))
# Draw the image on the display hardware.
print('Drawing image')
print("Drawing image")
disp.display(image)

View File

@@ -1,21 +1,19 @@
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont
import time
import ST7735
from PIL import Image, ImageDraw, ImageFont
import st7735
MESSAGE = "Hello World! How are you today?"
# Create ST7735 LCD display class.
disp = ST7735.ST7735(
disp = st7735.ST7735(
port=0,
cs=ST7735.BG_SPI_CS_FRONT,
dc=9,
backlight=18,
cs=st7735.BG_SPI_CS_FRONT, # BG_SPI_CS_BACK or BG_SPI_CS_FRONT. BG_SPI_CS_FRONT (eg: CE1) for Enviro Plus
dc="PIN21", # "GPIO9" / "PIN21". "PIN21" for a Pi 5 with Enviro Plus
backlight="PIN32", # "PIN18" for back BG slot, "PIN19" for front BG slot. "PIN32" for a Pi 5 with Enviro Plus
rotation=90,
spi_speed_hz=10000000
spi_speed_hz=4000000
)
# Initialize display.
@@ -31,7 +29,9 @@ draw = ImageDraw.Draw(img)
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 30)
size_x, size_y = draw.textsize(MESSAGE, font)
x1, y1, x2, y2 = font.getbbox(MESSAGE)
size_x = x2 - x1
size_y = y2 - y1
text_x = 160
text_y = (80 - size_y) // 2

View File

@@ -18,26 +18,24 @@
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont
from PIL import Image, ImageDraw, ImageFont
import ST7735
import st7735
print("""
shapes.py - Display test shapes on the LCD using PIL.
If you're using Breakout Garden, plug the 0.96" LCD (SPI)
If you"re using Breakout Garden, plug the 0.96" LCD (SPI)
breakout into the rear slot.
""")
# Create ST7735 LCD display class.
disp = ST7735.ST7735(
disp = st7735.ST7735(
port=0,
cs=ST7735.BG_SPI_CS_FRONT,
dc=9,
backlight=18,
cs=st7735.BG_SPI_CS_FRONT, # BG_SPI_CS_BACK or BG_SPI_CS_FRONT. BG_SPI_CS_FRONT (eg: CE1) for Enviro Plus
dc="PIN21", # "GPIO9" / "PIN21". "PIN21" for a Pi 5 with Enviro Plus
backlight="PIN32", # "PIN18" for back BG slot, "PIN19" for front BG slot. "PIN32" for a Pi 5 with Enviro Plus
rotation=90,
spi_speed_hz=4000000
)
@@ -52,7 +50,7 @@ HEIGHT = disp.height
# Clear the display to a red background.
# Can pass any tuple of red, green, blue values (from 0 to 255 each).
# Get a PIL Draw object to start drawing on the display buffer.
img = Image.new('RGB', (WIDTH, HEIGHT), color=(255, 0, 0))
img = Image.new("RGB", (WIDTH, HEIGHT), color=(255, 0, 0))
draw = ImageDraw.Draw(img)
@@ -75,18 +73,19 @@ font = ImageFont.load_default()
# Alternatively load a TTF font.
# Some other nice fonts to try: http://www.dafont.com/bitmap.php
# font = ImageFont.truetype('Minecraftia.ttf', 16)
# font = ImageFont.truetype("Minecraftia.ttf", 16)
# Define a function to create rotated text. Unfortunately PIL doesn't have good
# Define a function to create rotated text. Unfortunately PIL doesn"t have good
# native support for rotated fonts, but this function can be used to make a
# text image and rotate it so it's easy to paste in the buffer.
# text image and rotate it so it"s easy to paste in the buffer.
def draw_rotated_text(image, text, position, angle, font, fill=(255, 255, 255)):
# Get rendered font width and height.
draw = ImageDraw.Draw(image)
width, height = draw.textsize(text, font=font)
x1, y1, x2, y2 = font.getbbox(text)
width = x2 - x1
height = y2 - y1
# Create a new image with transparent background to store the text.
textimage = Image.new('RGBA', (width, height), (0, 0, 0, 0))
textimage = Image.new("RGBA", (width, height), (0, 0, 0, 0))
# Render the text.
textdraw = ImageDraw.Draw(textimage)
textdraw.text((0, 0), text, font=font, fill=fill)
@@ -97,8 +96,8 @@ def draw_rotated_text(image, text, position, angle, font, fill=(255, 255, 255)):
# Write two lines of white text on the buffer, rotated 90 degrees counter clockwise.
draw_rotated_text(img, 'Hello World!', (0, 0), 90, font, fill=(255, 255, 255))
draw_rotated_text(img, 'This is a line of text.', (10, HEIGHT - 10), 0, font, fill=(255, 255, 255))
draw_rotated_text(img, "Hello World!", (0, 0), 90, font, fill=(255, 255, 255))
draw_rotated_text(img, "This is a line of text.", (10, HEIGHT - 10), 0, font, fill=(255, 255, 255))
# Write buffer to display hardware, must be called to make things visible on the
# display!

315
install.sh Executable file
View File

@@ -0,0 +1,315 @@
#!/bin/bash
LIBRARY_NAME=`grep -m 1 name pyproject.toml | awk -F" = " '{print substr($2,2,length($2)-2)}'`
CONFIG_FILE=config.txt
CONFIG_DIR="/boot/firmware"
DATESTAMP=`date "+%Y-%m-%d-%H-%M-%S"`
CONFIG_BACKUP=false
APT_HAS_UPDATED=false
RESOURCES_TOP_DIR=$HOME/Pimoroni
VENV_BASH_SNIPPET=$RESOURCES_TOP_DIR/auto_venv.sh
VENV_DIR=$HOME/.virtualenvs/pimoroni
WD=`pwd`
USAGE="./install.sh (--unstable)"
POSITIONAL_ARGS=()
FORCE=false
UNSTABLE=false
PYTHON="python"
user_check() {
if [ $(id -u) -eq 0 ]; then
printf "Script should not be run as root. Try './install.sh'\n"
exit 1
fi
}
confirm() {
if $FORCE; then
true
else
read -r -p "$1 [y/N] " response < /dev/tty
if [[ $response =~ ^(yes|y|Y)$ ]]; then
true
else
false
fi
fi
}
prompt() {
read -r -p "$1 [y/N] " response < /dev/tty
if [[ $response =~ ^(yes|y|Y)$ ]]; then
true
else
false
fi
}
success() {
echo -e "$(tput setaf 2)$1$(tput sgr0)"
}
inform() {
echo -e "$(tput setaf 6)$1$(tput sgr0)"
}
warning() {
echo -e "$(tput setaf 1)$1$(tput sgr0)"
}
find_config() {
if [ ! -f "$CONFIG_DIR/$CONFIG_FILE" ]; then
CONFIG_DIR="/boot"
if [ ! -f "$CONFIG_DIR/$CONFIG_FILE"]; then
warning "Could not find $CONFIG_FILE!"
exit 1
fi
else
if [ -f "/boot/$CONFIG_FILE" ] && [ ! -L "/boot/$CONFIG_FILE" ]; then
warning "Oops! It looks like /boot/$CONFIG_FILE is not a link to $CONFIG_DIR/$CONFIG_FILE"
warning "You might want to fix this!"
fi
fi
inform "Using $CONFIG_FILE in $CONFIG_DIR"
}
venv_bash_snippet() {
inform "Checking for $VENV_BASH_SNIPPET\n"
if [ ! -f $VENV_BASH_SNIPPET ]; then
inform "Creating $VENV_BASH_SNIPPET\n"
cat << EOF > $VENV_BASH_SNIPPET
# Add "source $VENV_BASH_SNIPPET" to your ~/.bashrc to activate
# the Pimoroni virtual environment automagically!
VENV_DIR="$VENV_DIR"
if [ ! -f \$VENV_DIR/bin/activate ]; then
printf "Creating user Python environment in \$VENV_DIR, please wait...\n"
mkdir -p \$VENV_DIR
python3 -m venv --system-site-packages \$VENV_DIR
fi
printf " ↓ ↓ ↓ ↓ Hello, we've activated a Python venv for you. To exit, type \"deactivate\".\n"
source \$VENV_DIR/bin/activate
EOF
fi
}
venv_check() {
PYTHON_BIN=`which $PYTHON`
if [[ $VIRTUAL_ENV == "" ]] || [[ $PYTHON_BIN != $VIRTUAL_ENV* ]]; then
printf "This script should be run in a virtual Python environment.\n"
if confirm "Would you like us to create one for you?"; then
if [ ! -f $VENV_DIR/bin/activate ]; then
inform "Creating virtual Python environment in $VENV_DIR, please wait...\n"
mkdir -p $VENV_DIR
/usr/bin/python3 -m venv $VENV_DIR --system-site-packages
venv_bash_snippet
else
inform "Found existing virtual Python environment in $VENV_DIR\n"
fi
inform "Activating virtual Python environment in $VENV_DIR..."
inform "source $VENV_DIR/bin/activate\n"
source $VENV_DIR/bin/activate
else
exit 1
fi
fi
}
function do_config_backup {
if [ ! $CONFIG_BACKUP == true ]; then
CONFIG_BACKUP=true
FILENAME="config.preinstall-$LIBRARY_NAME-$DATESTAMP.txt"
inform "Backing up $CONFIG_DIR/$CONFIG_FILE to $CONFIG_DIR/$FILENAME\n"
sudo cp $CONFIG_DIR/$CONFIG_FILE $CONFIG_DIR/$FILENAME
mkdir -p $RESOURCES_TOP_DIR/config-backups/
cp $CONFIG_DIR/$CONFIG_FILE $RESOURCES_TOP_DIR/config-backups/$FILENAME
if [ -f "$UNINSTALLER" ]; then
echo "cp $RESOURCES_TOP_DIR/config-backups/$FILENAME $CONFIG_DIR/$CONFIG_FILE" >> $UNINSTALLER
fi
fi
}
function apt_pkg_install {
PACKAGES=()
PACKAGES_IN=("$@")
for ((i = 0; i < ${#PACKAGES_IN[@]}; i++)); do
PACKAGE="${PACKAGES_IN[$i]}"
if [ "$PACKAGE" == "" ]; then continue; fi
printf "Checking for $PACKAGE\n"
dpkg -L $PACKAGE > /dev/null 2>&1
if [ "$?" == "1" ]; then
PACKAGES+=("$PACKAGE")
fi
done
PACKAGES="${PACKAGES[@]}"
if ! [ "$PACKAGES" == "" ]; then
echo "Installing missing packages: $PACKAGES"
if [ ! $APT_HAS_UPDATED ]; then
sudo apt update
APT_HAS_UPDATED=true
fi
sudo apt install -y $PACKAGES
if [ -f "$UNINSTALLER" ]; then
echo "apt uninstall -y $PACKAGES" >> $UNINSTALLER
fi
fi
}
function pip_pkg_install {
PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring $PYTHON -m pip install --upgrade "$@"
}
while [[ $# -gt 0 ]]; do
K="$1"
case $K in
-u|--unstable)
UNSTABLE=true
shift
;;
-f|--force)
FORCE=true
shift
;;
-p|--python)
PYTHON=$2
shift
shift
;;
*)
if [[ $1 == -* ]]; then
printf "Unrecognised option: $1\n";
printf "Usage: $USAGE\n";
exit 1
fi
POSITIONAL_ARGS+=("$1")
shift
esac
done
user_check
venv_check
if [ ! -f `which $PYTHON` ]; then
printf "Python path $PYTHON not found!\n"
exit 1
fi
PYTHON_VER=`$PYTHON --version`
printf "$LIBRARY_NAME Python Library: Installer\n\n"
inform "Checking Dependencies. Please wait..."
pip_pkg_install toml
CONFIG_VARS=`$PYTHON - <<EOF
import toml
config = toml.load("pyproject.toml")
p = dict(config['tool']['pimoroni'])
# Convert list config entries into bash arrays
for k, v in p.items():
v = "'\n\t'".join(v)
p[k] = f"('{v}')"
print("""
APT_PACKAGES={apt_packages}
SETUP_CMDS={commands}
CONFIG_TXT={configtxt}
""".format(**p))
EOF`
if [ $? -ne 0 ]; then
warning "Error parsing configuration...\n"
exit 1
fi
eval $CONFIG_VARS
RESOURCES_DIR=$RESOURCES_TOP_DIR/$LIBRARY_NAME
UNINSTALLER=$RESOURCES_DIR/uninstall.sh
RES_DIR_OWNER=`stat -c "%U" $RESOURCES_TOP_DIR`
if [[ "$RES_DIR_OWNER" == "root" ]]; then
warning "\n\nFixing $RESOURCES_TOP_DIR permissions!\n\n"
sudo chown -R $USER:$USER $RESOURCES_TOP_DIR
fi
mkdir -p $RESOURCES_DIR
cat << EOF > $UNINSTALLER
printf "It's recommended you run these steps manually.\n"
printf "If you want to run the full script, open it in\n"
printf "an editor and remove 'exit 1' from below.\n"
exit 1
source $VIRTUAL_ENV/bin/activate
EOF
if $UNSTABLE; then
warning "Installing unstable library from source.\n\n"
else
printf "Installing stable library from pypi.\n\n"
fi
inform "Installing for $PYTHON_VER...\n"
apt_pkg_install "${APT_PACKAGES[@]}"
if $UNSTABLE; then
pip_pkg_install .
else
pip_pkg_install $LIBRARY_NAME
fi
if [ $? -eq 0 ]; then
success "Done!\n"
echo "$PYTHON -m pip uninstall $LIBRARY_NAME" >> $UNINSTALLER
fi
cd $WD
find_config
for ((i = 0; i < ${#SETUP_CMDS[@]}; i++)); do
CMD="${SETUP_CMDS[$i]}"
# Attempt to catch anything that touches config.txt and trigger a backup
if [[ "$CMD" == *"raspi-config"* ]] || [[ "$CMD" == *"$CONFIG_DIR/$CONFIG_FILE"* ]] || [[ "$CMD" == *"\$CONFIG_DIR/\$CONFIG_FILE"* ]]; then
do_config_backup
fi
eval $CMD
done
for ((i = 0; i < ${#CONFIG_TXT[@]}; i++)); do
CONFIG_LINE="${CONFIG_TXT[$i]}"
if ! [ "$CONFIG_LINE" == "" ]; then
do_config_backup
inform "Adding $CONFIG_LINE to $CONFIG_DIR/$CONFIG_FILE\n"
sudo sed -i "s/^#$CONFIG_LINE/$CONFIG_LINE/" $CONFIG_DIR/$CONFIG_FILE
if ! grep -q "^$CONFIG_LINE" $CONFIG_DIR/$CONFIG_FILE; then
printf "$CONFIG_LINE\n" | sudo tee --append $CONFIG_DIR/$CONFIG_FILE
fi
fi
done
if [ -d "examples" ]; then
if confirm "Would you like to copy examples to $RESOURCES_DIR?"; then
inform "Copying examples to $RESOURCES_DIR"
cp -r examples/ $RESOURCES_DIR
echo "rm -r $RESOURCES_DIR" >> $UNINSTALLER
success "Done!"
fi
fi
printf "\n"
if confirm "Would you like to generate documentation?"; then
pip_pkg_install pdoc
printf "Generating documentation.\n"
$PYTHON -m pdoc $LIBRARY_NAME -o $RESOURCES_DIR/docs > /dev/null
if [ $? -eq 0 ]; then
inform "Documentation saved to $RESOURCES_DIR/docs"
success "Done!"
else
warning "Error: Failed to generate documentation."
fi
fi
success "\nAll done!"
inform "If this is your first time installing you should reboot for hardware changes to take effect.\n"
inform "Find uninstall steps in $UNINSTALLER\n"

View File

@@ -1,4 +0,0 @@
[run]
source = ST7735
omit =
.tox/*

View File

@@ -1,17 +0,0 @@
0.0.3
-----
* Fixed backlight pin
* Added `set_backlight`
* Added constants BG_SPI_CS_FRONT and BG_SPI_CS_BACK
* Added module __version__
0.0.2
-----
* Support for multiple display sizes/orientations
0.0.1
-----
* Initial Release

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2018 Pimoroni Ltd.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,5 +0,0 @@
include CHANGELOG.txt
include LICENSE.txt
include README.rst
include setup.py
recursive-include ST7735 *.py

View File

@@ -1,71 +0,0 @@
Python ST7735
=============
|Build Status| |Coverage Status| |PyPi Package| |Python Versions|
Python library to control an ST7735 TFT LCD display. Allows simple
drawing on the display without installing a kernel module.
Designed specifically to work with a ST7735 based 160x80 pixel TFT SPI
display. (Specifically the 0.96" SPI LCD from Pimoroni).
Make sure you have the following dependencies:
::
sudo apt-get update
sudo apt-get install python-rpi.gpio python-spidev python-pip python-imaging python-numpy
Install this library by running:
::
sudo pip install st7735
See example of usage in the examples folder.
Licensing & History
===================
This library is a modification of a modification of code originally
written by Tony DiCola for Adafruit Industries, and modified to work
with the ST7735 by Clement Skau.
It has been modified by Pimoroni to include support for their 160x80 SPI
LCD breakout, and hopefully also generalised enough so that it will
support other ST7735-powered displays.
Modifications include:
----------------------
- PIL/Pillow has been removed from the underlying display driver to
separate concerns- you should create your own PIL image and display
it using ``display(image)``
- ``width``, ``height``, ``rotation``, ``invert``, ``offset_left`` and
``offset_top`` parameters can be passed into ``__init__`` for
alternate displays
- ``Adafruit_GPIO`` has been replaced with ``RPi.GPIO`` and ``spidev``
to closely align with our other software (IE: Raspberry Pi only)
- Test fixtures have been added to keep this library stable
Pimoroni invests time and resources forking and modifying this open
source code, please support Pimoroni and open-source software by
purchasing products from us, too!
Adafruit invests time and resources providing this open source code,
please support Adafruit and open-source hardware by purchasing products
from Adafruit!
Modified from 'Modified from 'Adafruit Python ILI9341' written by Tony
DiCola for Adafruit Industries.' written by Clement Skau.
MIT license, all text above must be included in any redistribution
.. |Build Status| image:: https://travis-ci.com/pimoroni/st7735-python.svg?branch=master
:target: https://travis-ci.com/pimoroni/st7735-python
.. |Coverage Status| image:: https://coveralls.io/repos/github/pimoroni/st7735-python/badge.svg?branch=master
:target: https://coveralls.io/github/pimoroni/st7735-python?branch=master
.. |PyPi Package| image:: https://img.shields.io/pypi/v/st7735.svg
:target: https://pypi.python.org/pypi/st7735
.. |Python Versions| image:: https://img.shields.io/pypi/pyversions/st7735.svg
:target: https://pypi.python.org/pypi/st7735

View File

@@ -1,11 +0,0 @@
[flake8]
exclude =
test.py
.tox,
.eggs,
.git,
__pycache__,
build,
dist
ignore =
E501

View File

@@ -1,22 +0,0 @@
from setuptools import setup, find_packages
classifiers = ['Development Status :: 4 - Beta',
'Operating System :: POSIX :: Linux',
'License :: OSI Approved :: MIT License',
'Intended Audience :: Developers',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Topic :: Software Development',
'Topic :: System :: Hardware']
setup(name='ST7735',
version='0.0.3',
description='Library to control an ST7735 168x80 TFT LCD display.',
long_description=open('README.rst').read() + '\n' + open('CHANGELOG.txt').read(),
license='MIT',
author='Philip Howard',
author_email='phil@pimoroni.com',
classifiers=classifiers,
url='https://github.com/pimoroni/st7735-160x80-python/',
packages=find_packages())

View File

@@ -1,25 +0,0 @@
import sys
import mock
def _mock():
sys.modules['numpy'] = mock.Mock()
sys.modules['spidev'] = mock.Mock()
sys.modules['RPi'] = mock.Mock()
sys.modules['RPi.GPIO'] = mock.Mock()
def test_128_64_0():
_mock()
import ST7735
display = ST7735.ST7735(port=0, cs=0, dc=24, width=128, height=64, rotation=0)
assert display.width == 128
assert display.height == 64
def test_128_64_90():
_mock()
import ST7735
display = ST7735.ST7735(port=0, cs=0, dc=24, width=128, height=64, rotation=90)
assert display.width == 64
assert display.height == 128

View File

@@ -1,13 +0,0 @@
import sys
import mock
def test_setup():
sys.modules['numpy'] = mock.Mock()
sys.modules['spidev'] = mock.Mock()
sys.modules['RPi'] = mock.Mock()
sys.modules['RPi.GPIO'] = mock.Mock()
import ST7735
display = ST7735.ST7735(port=0, cs=0, dc=24)
del display

View File

@@ -1,21 +0,0 @@
[tox]
envlist = py{27,35},qa
skip_missing_interpreters = True
[testenv]
commands =
python setup.py install
coverage run -m py.test -v -r wsx
coverage report
deps =
mock
pytest>=3.1
pytest-cov
[testenv:qa]
commands =
flake8 --ignore E501
rstcheck README.rst
deps =
flake8
rstcheck

124
pyproject.toml Normal file
View File

@@ -0,0 +1,124 @@
[build-system]
requires = ["hatchling", "hatch-fancy-pypi-readme"]
build-backend = "hatchling.build"
[project]
name = "st7735"
dynamic = ["version", "readme"]
description = "Library to control an ST7735 168x80 TFT LCD display."
license = {file = "LICENSE"}
requires-python = ">= 3.7"
authors = [
{ name = "Philip Howard", email = "phil@pimoroni.com" },
]
maintainers = [
{ name = "Philip Howard", email = "phil@pimoroni.com" },
]
keywords = [
"Pi",
"Raspberry",
"displays"
]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: POSIX :: Linux",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3 :: Only",
"Topic :: Software Development",
"Topic :: Software Development :: Libraries",
"Topic :: System :: Hardware",
]
dependencies = [
"spidev>=3.4",
"numpy"
]
[project.urls]
GitHub = "https://www.github.com/pimoroni/st7735-python"
Homepage = "https://www.pimoroni.com"
[tool.hatch.version]
path = "st7735/__init__.py"
[tool.hatch.build]
include = [
"st7735",
"ST7735.py",
"README.md",
"CHANGELOG.md",
"LICENSE"
]
[tool.hatch.build.targets.sdist]
include = [
"*"
]
exclude = [
".*",
"dist"
]
[tool.hatch.metadata.hooks.fancy-pypi-readme]
content-type = "text/markdown"
fragments = [
{ path = "README.md" },
{ text = "\n" },
{ path = "CHANGELOG.md" }
]
[tool.ruff]
exclude = [
'.tox',
'.egg',
'.git',
'__pycache__',
'build',
'dist'
]
line-length = 200
[tool.codespell]
skip = """
./.tox,\
./.egg,\
./.git,\
./__pycache__,\
./build,\
./dist.\
"""
[tool.isort]
line_length = 200
[tool.black]
line-length = 200
[tool.check-manifest]
ignore = [
'.stickler.yml',
'boilerplate.md',
'check.sh',
'install.sh',
'uninstall.sh',
'Makefile',
'tox.ini',
'tests/*',
'examples/*',
'.coveragerc',
'requirements-dev.txt'
]
[tool.pimoroni]
apt_packages = []
configtxt = []
commands = [
"printf \"Setting up SPI...\n\"",
"sudo raspi-config nonint do_spi 0"
]

9
requirements-dev.txt Normal file
View File

@@ -0,0 +1,9 @@
check-manifest
ruff
codespell
isort
twine
hatch
hatch-fancy-pypi-readme
tox
pdoc

3
requirements.txt Normal file
View File

@@ -0,0 +1,3 @@
pillow>=10.0.0
numpy>=1.26.0
spidev>=3.4

View File

@@ -20,13 +20,16 @@
# THE SOFTWARE.
import numbers
import time
import gpiod
import gpiodevice
import numpy as np
import spidev
import RPi.GPIO as GPIO
from gpiod.line import Direction, Value
__version__ = '1.0.0'
__version__ = '0.0.3'
OUTL = gpiod.LineSettings(direction=Direction.OUTPUT, output_value=Value.INACTIVE)
BG_SPI_CS_BACK = 0
BG_SPI_CS_FRONT = 1
@@ -50,15 +53,8 @@ ST7735_SLPOUT = 0x11
ST7735_PTLON = 0x12
ST7735_NORON = 0x13
# ILI9341_RDMODE = 0x0A
# ILI9341_RDMADCTL = 0x0B
# ILI9341_RDPIXFMT = 0x0C
# ILI9341_RDIMGFMT = 0x0A
# ILI9341_RDSELFDIAG = 0x0F
ST7735_INVOFF = 0x20
ST7735_INVON = 0x21
# ILI9341_GAMMASET = 0x26
ST7735_DISPOFF = 0x28
ST7735_DISPON = 0x29
@@ -69,14 +65,12 @@ ST7735_RAMRD = 0x2E
ST7735_PTLAR = 0x30
ST7735_MADCTL = 0x36
# ST7735_PIXFMT = 0x3A
ST7735_COLMOD = 0x3A
ST7735_FRMCTR1 = 0xB1
ST7735_FRMCTR2 = 0xB2
ST7735_FRMCTR3 = 0xB3
ST7735_INVCTR = 0xB4
# ILI9341_DFUNCTR = 0xB6
ST7735_DISSET5 = 0xB6
@@ -86,7 +80,6 @@ ST7735_PWCTR3 = 0xC2
ST7735_PWCTR4 = 0xC3
ST7735_PWCTR5 = 0xC4
ST7735_VMCTR1 = 0xC5
# ILI9341_VMCTR2 = 0xC7
ST7735_RDID1 = 0xDA
ST7735_RDID2 = 0xDB
@@ -129,12 +122,12 @@ class ST7735(object):
"""Representation of an ST7735 TFT LCD."""
def __init__(self, port, cs, dc, backlight=None, rst=None, width=ST7735_TFTWIDTH,
height=ST7735_TFTHEIGHT, rotation=90, offset_left=None, offset_top=None, invert=True, spi_speed_hz=4000000):
height=ST7735_TFTHEIGHT, rotation=90, offset_left=None, offset_top=None, invert=True, bgr=True, spi_speed_hz=4000000):
"""Create an instance of the display using SPI communication.
Must provide the GPIO pin number for the D/C pin and the SPI driver.
Must provide the GPIO pin label for the D/C pin and the SPI driver.
Can optionally provide the GPIO pin number for the reset pin as the rst parameter.
Can optionally provide the GPIO pin label for the reset pin as the rst parameter.
:param port: SPI port number
:param cs: SPI chip-select number (0 or 1 for BCM
@@ -150,20 +143,19 @@ class ST7735(object):
"""
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
self._spi = spidev.SpiDev(port, cs)
self._spi.mode = 0
self._spi.lsbfirst = False
self._spi.max_speed_hz = spi_speed_hz
self._dc = dc
self._rst = rst
self._width = width
self._height = height
self._rotation = rotation
self._invert = invert
self._bgr = bgr
self._bl = None
self._rst = None
# Default left offset to center display
if offset_left is None:
@@ -177,24 +169,29 @@ class ST7735(object):
self._offset_top = offset_top
gpiodevice.friendly_errors = True
# Set DC as output.
GPIO.setup(dc, GPIO.OUT)
self._dc = gpiodevice.get_pin(dc, "st7735-dc", OUTL)
# Setup backlight as output (if provided).
self._backlight = backlight
if backlight is not None:
GPIO.setup(backlight, GPIO.OUT)
GPIO.output(backlight, GPIO.LOW)
self._bl = gpiodevice.get_pin(backlight, "st7735-bl", OUTL)
self.set_pin(self._bl, False)
time.sleep(0.1)
GPIO.output(backlight, GPIO.HIGH)
self.set_pin(self._bl, True)
# Setup reset as output (if provided).
if rst is not None:
GPIO.setup(rst, GPIO.OUT)
self._rst = gpiodevice.get_pin(rst, "st7735-rst", OUTL)
self.reset()
self._init()
def set_pin(self, pin, state):
lines, offset = pin
lines.set_value(offset, Value.ACTIVE if state else Value.INACTIVE)
def send(self, data, is_data=True, chunk_size=4096):
"""Write a byte or array of bytes to the display. Is_data parameter
controls if byte should be interpreted as display data (True) or command
@@ -202,19 +199,28 @@ class ST7735(object):
single SPI transaction, with a default of 4096.
"""
# Set DC low for command, high for data.
GPIO.output(self._dc, is_data)
self.set_pin(self._dc, is_data)
# Convert scalar argument to list so either can be passed as parameter.
if isinstance(data, numbers.Number):
data = [data & 0xFF]
# Write data a chunk at a time.
for start in range(0, len(data), chunk_size):
end = min(start + chunk_size, len(data))
self._spi.xfer(data[start:end])
self._spi.xfer3(data)
def set_backlight(self, value):
"""Set the backlight on/off."""
if self._backlight is not None:
GPIO.output(self._backlight, value)
if self._bl is not None:
self.set_pin(self._bl, value)
def display_off(self):
self.command(ST7735_DISPOFF)
def display_on(self):
self.command(ST7735_DISPON)
def sleep(self):
self.command(ST7735_SLPIN)
def wake(self):
self.command(ST7735_SLPOUT)
@property
def width(self):
@@ -235,11 +241,11 @@ class ST7735(object):
def reset(self):
"""Reset the display, if reset pin is connected."""
if self._rst is not None:
GPIO.output(self._rst, 1)
self.set_pin(self._rst, True)
time.sleep(0.500)
GPIO.output(self._rst, 0)
self.set_pin(self._rst, False)
time.sleep(0.500)
GPIO.output(self._rst, 1)
self.set_pin(self._rst, True)
time.sleep(0.500)
def _init(self):
@@ -298,7 +304,10 @@ class ST7735(object):
self.command(ST7735_INVOFF) # Don't invert display
self.command(ST7735_MADCTL) # Memory access control (directions)
self.data(0xC8) # row addr/col addr, bottom to top refresh
if self._bgr:
self.data(0xC8) # row addr/col addr, bottom to top refresh; Set D3 RGB Bit to 1 for format BGR
else:
self.data(0xC0) # row addr/col addr, bottom to top refresh; Set D3 RGB Bit to 0 for format RGB
self.command(ST7735_COLMOD) # set color mode
self.data(0x05) # 16-bit color
@@ -354,7 +363,7 @@ class ST7735(object):
self.command(ST7735_NORON) # Normal display on
time.sleep(0.10) # 10 ms
self.command(ST7735_DISPON) # Display on
self.display_on()
time.sleep(0.100) # 100 ms
def begin(self):

51
tests/conftest.py Normal file
View File

@@ -0,0 +1,51 @@
"""Test configuration.
These allow the mocking of various Python modules
that might otherwise have runtime side-effects.
"""
import sys
import mock
import pytest
@pytest.fixture(scope="function", autouse=False)
def st7735():
import st7735
yield st7735
del sys.modules["st7735"]
@pytest.fixture(scope="function", autouse=False)
def gpiod():
"""Mock gpiod module."""
sys.modules["gpiod"] = mock.MagicMock()
sys.modules["gpiod.line"] = mock.MagicMock()
yield sys.modules["gpiod"]
del sys.modules["gpiod"]
@pytest.fixture(scope="function", autouse=False)
def gpiodevice():
"""Mock gpiodevice module."""
sys.modules["gpiodevice"] = mock.MagicMock()
sys.modules["gpiodevice"].get_pin.return_value = (mock.Mock(), 0)
yield sys.modules["gpiodevice"]
del sys.modules["gpiodevice"]
@pytest.fixture(scope="function", autouse=False)
def spidev():
"""Mock spidev module."""
spidev = mock.MagicMock()
sys.modules["spidev"] = spidev
yield spidev
del sys.modules["spidev"]
@pytest.fixture(scope="function", autouse=False)
def numpy():
"""Mock numpy module."""
numpy = mock.MagicMock()
sys.modules["numpy"] = numpy
yield numpy
del sys.modules["numpy"]

10
tests/test_dimensions.py Normal file
View File

@@ -0,0 +1,10 @@
def test_128_64_0(gpiodevice, gpiod, spidev, numpy, st7735):
display = st7735.ST7735(port=0, cs=0, dc=24, width=128, height=64, rotation=0)
assert display.width == 128
assert display.height == 64
def test_128_64_90(gpiodevice, gpiod, spidev, numpy, st7735):
display = st7735.ST7735(port=0, cs=0, dc=24, width=128, height=64, rotation=90)
assert display.width == 64
assert display.height == 128

18
tests/test_features.py Normal file
View File

@@ -0,0 +1,18 @@
import mock
def test_display(gpiodevice, gpiod, spidev, numpy, st7735):
display = st7735.ST7735(port=0, cs=0, dc=24)
numpy.dstack().flatten().tolist.return_value = [0xff, 0x00, 0xff, 0x00]
display.display(mock.MagicMock())
spidev.SpiDev().xfer3.assert_called_with([0xff, 0x00, 0xff, 0x00])
def test_color565(gpiodevice, gpiod, spidev, numpy, st7735):
assert st7735.color565(255, 255, 255) == 0xFFFF
def test_image_to_data(gpiodevice, gpiod, spidev, numpy, st7735):
numpy.dstack().flatten().tolist.return_value = []
assert st7735.image_to_data(mock.MagicMock()) == []

27
tests/test_setup.py Normal file
View File

@@ -0,0 +1,27 @@
import mock
def test_setup(gpiodevice, gpiod, spidev, numpy, st7735):
_ = st7735.ST7735(port=0, cs=0, dc="GPIO24")
gpiodevice.get_pin.assert_has_calls([
mock.call("GPIO24", "st7735-dc", st7735.OUTL)
], any_order=True)
def test_setup_no_invert(gpiodevice, gpiod, spidev, numpy, st7735):
_ = st7735.ST7735(port=0, cs=0, dc="GPIO24", invert=False)
def test_setup_with_backlight(gpiodevice, gpiod, spidev, numpy, st7735):
display = st7735.ST7735(port=0, cs=0, dc="GPIO24", backlight="GPIO4")
display.set_backlight(True)
gpiodevice.get_pin.assert_has_calls([mock.call("GPIO4", "st7735-bl", st7735.OUTL)], any_order=True)
def test_setup_with_reset(gpiodevice, gpiod, spidev, numpy, st7735):
_ = st7735.ST7735(port=0, cs=0, dc=24, rst="GPIO4")
gpiodevice.get_pin.assert_has_calls([mock.call("GPIO4", "st7735-rst", st7735.OUTL)], any_order=True)

34
tox.ini Normal file
View File

@@ -0,0 +1,34 @@
[tox]
envlist = py,qa
skip_missing_interpreters = True
isolated_build = true
minversion = 4.0.0
[testenv]
commands =
coverage run -m pytest -v -r wsx
coverage report
deps =
mock
pytest>=3.1
pytest-cov
build
[testenv:qa]
commands =
check-manifest
python -m build --no-isolation
python -m twine check dist/*
isort --check .
ruff .
codespell .
deps =
check-manifest
ruff
codespell
isort
twine
build
hatch
hatch-fancy-pypi-readme

72
uninstall.sh Executable file
View File

@@ -0,0 +1,72 @@
#!/bin/bash
FORCE=false
LIBRARY_NAME=`grep -m 1 name pyproject.toml | awk -F" = " '{print substr($2,2,length($2)-2)}'`
RESOURCES_DIR=$HOME/Pimoroni/$LIBRARY_NAME
PYTHON="python"
venv_check() {
PYTHON_BIN=`which $PYTHON`
if [[ $VIRTUAL_ENV == "" ]] || [[ $PYTHON_BIN != $VIRTUAL_ENV* ]]; then
printf "This script should be run in a virtual Python environment.\n"
exit 1
fi
}
user_check() {
if [ $(id -u) -eq 0 ]; then
printf "Script should not be run as root. Try './uninstall.sh'\n"
exit 1
fi
}
confirm() {
if $FORCE; then
true
else
read -r -p "$1 [y/N] " response < /dev/tty
if [[ $response =~ ^(yes|y|Y)$ ]]; then
true
else
false
fi
fi
}
prompt() {
read -r -p "$1 [y/N] " response < /dev/tty
if [[ $response =~ ^(yes|y|Y)$ ]]; then
true
else
false
fi
}
success() {
echo -e "$(tput setaf 2)$1$(tput sgr0)"
}
inform() {
echo -e "$(tput setaf 6)$1$(tput sgr0)"
}
warning() {
echo -e "$(tput setaf 1)$1$(tput sgr0)"
}
printf "$LIBRARY_NAME Python Library: Uninstaller\n\n"
user_check
venv_check
printf "Uninstalling for Python 3...\n"
$PYTHON -m pip uninstall $LIBRARY_NAME
if [ -d $RESOURCES_DIR ]; then
if confirm "Would you like to delete $RESOURCES_DIR?"; then
rm -r $RESOURCES_DIR
fi
fi
printf "Done!\n"