W tym poście chciałbym opisać sposób zmiany częstotliwości PWM.
Zaczynamy od sprawdzenia z jakim systemem mamy do czynienia:
- cat /etc/os-release
- VERSION_ID="v2017.12"
- NAME="Angstrom"
- ID="angstrom"
- PRETTY_NAME="The Ångström Distribution v2017.12"
- ANSI_COLOR="1;35"
- HOME_URL="http://www.angstrom-distribution.org"
- uname -r
- 4.9.166-00001-g6bd85ffba9f2-dirty
- cat /etc/version 2>/dev/null
- Angstrom v2017.12
Jak widać powyżej moduł to Toradex Colibri iMX6DL/S z systemem The Ångström Distribution v2017.12. Urządzenie pracuje pod kernelem Linux 4.9.166-00001-g6bd85ffba9f2-dirty. Architektura ARMv7. W nim podświetlenie LCD jest obsługiwane przez standardowy sterownik pwm-backlight. Jego konfiguracja ustawiana jest z Device Tree.
Na początku sprawdzam urządzenia klasy backlight:
- ls -l /sys/class/backlight/
- total 0
- lrwxrwxrwx 1 root root 0 Apr 1 2019 backlight -> ../../devices/soc0/backlight/backlight/backlight
Jak widać jest tutaj device/soc0/backlight/backlight/backlight/
Przechodzimy do sprawdzenia zakresu jasności:
- cd /sys/class/backlight/backlight
- /sys/class/backlight/backlight# cat brightness
- 4
- /sys/class/backlight/backlight# cat actual_brightness
- 4
- /sys/class/backlight/backlight# cat max_brightness
- 7
- /sys/class/backlight/backlight# cat type
- raw
- /sys/class/backlight/backlight#
Zakres jasności działa w przedziale od 0 do 7.
Teraz sprawdzam główny sterownika:
- /sys/class/backlight/backlight# cd /sys/devices/soc0/backlight
- /sys/devices/soc0/backlight# readlink -f driver
- /sys/bus/platform/drivers/pwm-backlight
- /sys/devices/soc0/backlight#
Z wyniku komendy można stwierdzić, że za sterowaniem podświetleniem stoi standardowy sterownik kernela.
Czas na weryfikację device-tree:
- /sys/devices/soc0/backlight# readlink -f of_node
- /sys/firmware/devicetree/base/backlight
- /sys/devices/soc0/backlight# ls /sys/firmware/devicetree/base/backlight
- brightness-levels default-brightness-level name pinctrl-names status
- compatible enable-gpios pinctrl-0 pwms
Jak można się domyślić interesuje nas pwms. Więc wykonujemy hexdump w celu odczytania parametrów:
- /sys/devices/soc0/backlight# hexdump -C /sys/firmware/devicetree/base/backlight/pwms
- 00000000 00 00 00 63 00 00 00 00 00 4c 4b 40 00 00 00 01 |...c.....LK@....|
- 00000010s
Na samym początku musimy zinterpretować te dane link.
- bl: backlight {
- pwms = <&pwm 0 5000000 PWM_POLARITY_INVERTED>;
- pwm-names = "backlight";
- };
PWMS składa się z: phandle (identyfikator węzła) do kontrolera PWM, numeru kanału PWM, okresu PWM w nanosekundach oraz flag np. polaryzacja. Dane pogrupowane są w bloki 32 bitowe:
- 0x00000063 - phandle
- 0x00000000 - numer kanału PWM
- 0x004C4B40 - okres PWM w nanosekundach 5000000.
- 0x000000001 - flaga Inverted Polarity.
Flaga Inverted Polarity jest tak zdefiniowana w systemie:
- #ifndef _DT_BINDINGS_PWM_PWM_H
- #define _DT_BINDINGS_PWM_PWM_H
- #define PWM_POLARITY_INVERTED (1 << 0)
- #endif
W device tree zdefiniowana jest także tablica z poziomem jasności:
- /sys/devices/soc0/backlight# hexdump -C /sys/firmware/devicetree/base/backlight/brightness-levels
- 00000000 00 00 00 00 00 00 00 04 00 00 00 08 00 00 00 10 |................|
- 00000010 00 00 00 20 00 00 00 40 00 00 00 80 00 00 00 ff |... ...@........|
- 00000020
Czyli jest 0 (0), 4 (1), 8 (2), 16 (3), 32 (4), 64 (5), 128 (6), 255 (7). Te wartości są używane do wyliczenia współczynnika wypełnienia.
Jak jest wybrany poziom jasności na 4:
- 32/255 = 0.1254 => około 12,5%
- 5000000ns * (32/255) = 627450 ns
Wyliczenia można porównać z danymi zapisanymi w systemie:
- /sys/devices/soc0/backlight# cat /sys/kernel/debug/pwm
- platform/2088000.pwm, 1 PWM device
- pwm-0 (backlight ): requested enabled period: 5000000 ns duty: 627450 ns polarity: inverse
W przykładowym układzie jest zastosowany sterownik podświetlenia PAM2841. Sterowy jest sygnałem PWM w zakresie 100–500 Hz, dlatego testowane częstotliwości zostały ograniczone do tego przedziału. Czyli można spokojnie zmieniać częstotliwość w takim zakresie.
Wartości obliczamy z wzoru:
- okres[ns] = 1 000 000 000 / częstotliwość w hercach
Dla dopuszczalnego zakresu częstotliwości dostajemy następujące wartości:
- 100Hz - 10 000 000ns - 0x00989680
- 200Hz - 5 000 000ns - 0x004C4B40
- 300Hz - 3 333 333ns - 0x0032DCD5
- 400Hz - 2 500 000ns - 0x002625A0
- 500Hz - 2 000 000ns - 0x001E8480
Aby wprowadzić zmiant, najprościej jest użyć skryptu w pythonie, który wpisze dane w odpowiednie miejsce w pliku. Wcześniej jeszcze należy określić miejsce w którym te zmiany trzeba będzie wprowadzić.
W pliku DTB należy wyszukać charakterystycznego ciągu znaków. Odpowiada on wcześniej odczytanym parametrom z pwms. Do wyszukiwania należy dołożyć bajty poprzedzające, tak by łatwo zidentyfikować miejsce wystąpienia wpisu.
- /sys/devices/soc0/backlight# hexdump -C /media/mmcblk0p1/imx6dl-colibri-rcpkd_pb.dtb | grep "00 00 00 00 00 4c 4b 40"
- 0000b110 00 00 09 69 00 00 00 63 00 00 00 00 00 4c 4b 40 |...i...c.....LK@|
Czyli dane należy wpisać z przesunięciem o 0xB110 + bajty w linii do początku 00 4C 4B 40. Czyli 0xB110 + 0x000C (12DEC).
Na samym początku kopiujemy plik .dtb. Tak aby zmiany wprowadzać na pliku pomocnicznym. Dodatkowe zabezpieczenie by nic nie zepsuć.
- cp /media/mmcblk0p1/imx6dl-colibri-rcpkd_pb.dtb \
- /tmp/imx6dl-colibri-rcpkd_pb.dtb
Warto też wykonać kopie zapasową. Tak na wszelki wypadek:
- cp /media/mmcblk0p1/imx6dl-colibri-rcpkd_pb.dtb \
- /media/mmcblk0p1/imx6dl-colibri-rcpkd_pb.dtb.bak
Teraz tworzymy skrypt:
- path = "/tmp/imx6dl-colibri-rcpkd_pb.dtb"
- offset = 0xB11C
- f = open(path, "r+b")
- f.seek(offset)
- #f.write("\x00\x98\x96\x80") #100Hz
- #f.write("\x00\x4C\x4B\x40") #200Hz
- #f.write("\x00\x32\xDC\xD5") #300Hz
- #f.write("\x00\x26\x25\xA0") #400Hz
- f.write("\x00\x1E\x84\x80") #500Hz
- f.close()
- print("OK: ustawiono 500 Hz")
Do skryptu można też dołożyć weryfikację by nie nadpisać złego miejsca w pamięci. Można to wykać przez sprawdzenie co jest wpisane w miejscu w którym będzie dokonywana zmiana.
Teraz należy uruchomić skrypt i sprawdzić czy wszystko przeszło poprawnie:
- ~# cp /media/mmcblk0p1/imx6dl-colibri-rcpkd_pb.dtb \
- > /tmp/imx6dl-colibri-rcpkd_pb.dtb
- ~# python c_pwm.py
- OK: ustawiono 500 Hz
- ~# ^C
- ~# dd if=/tmp/imx6dl-colibri-rcpkd_pb.dtb bs=1 skip=45332 count=24 2>/dev/null | hexdump -C
- 00000000 00 00 00 63 00 00 00 00 00 1e 84 80 00 00 00 01 |...c............|
- 00000010 00 00 00 03 00 00 00 20 |....... |
- 00000018
Jeszcze można tak:
- ~# hexdump -C /tmp/imx6dl-colibri-rcpkd_pb.dtb | grep "00 00 00 00 1e 84 80"
- 0000b110 00 00 09 69 00 00 00 63 00 00 00 00 00 1e 84 80 |...i...c........|
Jak widać dane zostały wpisane. Teraz należy skopiować plik i wykonać komendę sync oraz reboot:
- ~# cp /tmp/imx6dl-colibri-rcpkd_pb.dtb \
- /media/mmcblk0p1/imx6dl-colibri-rcpkd_pb.dtb
- ~# sync
- ~# reboot
Po restarcie należy zweryfikować czy wszystko poszło dobrze:
- ~# cat /sys/kernel/debug/pwm
- platform/2088000.pwm, 1 PWM device
- pwm-0 (backlight ): requested enabled period: 2000000 ns duty: 250980 ns polarity: inverse
Jak widać modyfikacje zostały wprowadzone poprawnie.
Taka bezpośrednia edycja plik DBT zależy od konkretnej wersji pliku. Tak samo offset czy dtb może pokazywać inne dane niż w powyższym przykładzie. Więc obowiązkowo przed zmianą należy wykonać kopie zapasową.