środa, 30 września 2026

Toradex Colibri - Zmiana częstotliwości PWM dla podświetlenia wyświetlacza

W tym poście chciałbym opisać sposób zmiany częstotliwości PWM.


Zaczynamy od sprawdzenia z jakim systemem mamy do czynienia:

  1. cat /etc/os-release
  2. VERSION_ID="v2017.12"
  3. NAME="Angstrom"
  4. ID="angstrom"
  5. PRETTY_NAME="The Ångström Distribution v2017.12"
  6. ANSI_COLOR="1;35"
  7. HOME_URL="http://www.angstrom-distribution.org"
      
  1. uname -r
  2. 4.9.166-00001-g6bd85ffba9f2-dirty
  3.  
  4. cat /etc/version 2>/dev/null
  5. 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:

  1. ls -l /sys/class/backlight/
  2. total 0
  3. 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:

  1. cd /sys/class/backlight/backlight
  2. /sys/class/backlight/backlight# cat brightness
  3. 4
  4. /sys/class/backlight/backlight# cat actual_brightness
  5. 4
  6. /sys/class/backlight/backlight# cat max_brightness
  7. 7
  8. /sys/class/backlight/backlight# cat type
  9. raw
  10. /sys/class/backlight/backlight#

Zakres jasności działa w przedziale od 0 do 7. 

Teraz sprawdzam główny sterownika:

  1. /sys/class/backlight/backlight# cd /sys/devices/soc0/backlight
  2. /sys/devices/soc0/backlight# readlink -f driver
  3. /sys/bus/platform/drivers/pwm-backlight
  4. /sys/devices/soc0/backlight#

Z wyniku komendy można stwierdzić, że za sterowaniem podświetleniem stoi standardowy sterownik kernela.

Czas na weryfikację device-tree:

  1. /sys/devices/soc0/backlight# readlink -f of_node
  2. /sys/firmware/devicetree/base/backlight
  3.  
  4. /sys/devices/soc0/backlight# ls /sys/firmware/devicetree/base/backlight
  5. brightness-levels  default-brightness-level  name       pinctrl-names  status
  6. compatible         enable-gpios              pinctrl-0  pwms

Jak można się domyślić interesuje nas pwms. Więc wykonujemy hexdump w celu odczytania parametrów:

  1. /sys/devices/soc0/backlight# hexdump -C /sys/firmware/devicetree/base/backlight/pwms
  2. 00000000  00 00 00 63 00 00 00 00  00 4c 4b 40 00 00 00 01  |...c.....LK@....|
  3. 00000010s

Na samym początku musimy zinterpretować te dane link. 

  1. bl: backlight {
  2.     pwms = <&pwm 0 5000000 PWM_POLARITY_INVERTED>;
  3.     pwm-names = "backlight";
  4. };

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:

  1. #ifndef _DT_BINDINGS_PWM_PWM_H
  2. #define _DT_BINDINGS_PWM_PWM_H
  3.  
  4. #define PWM_POLARITY_INVERTED           (1 << 0)
  5.  
  6. #endif

W device tree zdefiniowana jest także tablica z poziomem jasności:

  1. /sys/devices/soc0/backlight# hexdump -C /sys/firmware/devicetree/base/backlight/brightness-levels
  2. 00000000  00 00 00 00 00 00 00 04  00 00 00 08 00 00 00 10  |................|
  3. 00000010  00 00 00 20 00 00 00 40  00 00 00 80 00 00 00 ff  |... ...@........|
  4. 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:

  1. 32/255 = 0.1254 => około 12,5%
  2. 5000000ns * (32/255) = 627450 ns

Wyliczenia można porównać z danymi zapisanymi w systemie:

  1. /sys/devices/soc0/backlight# cat /sys/kernel/debug/pwm
  2. platform/2088000.pwm, 1 PWM device
  3.  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:

  1. 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. 

  1. /sys/devices/soc0/backlight# hexdump -C /media/mmcblk0p1/imx6dl-colibri-rcpkd_pb.dtb | grep "00 00 00 00 00 4c 4b 40"
  2. 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ć.

    1. cp /media/mmcblk0p1/imx6dl-colibri-rcpkd_pb.dtb \
    2.    /tmp/imx6dl-colibri-rcpkd_pb.dtb

Warto też wykonać kopie zapasową. Tak na wszelki wypadek:

  1. cp /media/mmcblk0p1/imx6dl-colibri-rcpkd_pb.dtb \
  2.    /media/mmcblk0p1/imx6dl-colibri-rcpkd_pb.dtb.bak

Teraz tworzymy skrypt:

  1. path = "/tmp/imx6dl-colibri-rcpkd_pb.dtb"
  2. offset = 0xB11C
  3.  
  4. f = open(path, "r+b")
  5. f.seek(offset)
  6. #f.write("\x00\x98\x96\x80") #100Hz
  7. #f.write("\x00\x4C\x4B\x40") #200Hz
  8. #f.write("\x00\x32\xDC\xD5") #300Hz
  9. #f.write("\x00\x26\x25\xA0") #400Hz
  10. f.write("\x00\x1E\x84\x80") #500Hz
  11. f.close()
  12.  
  13. 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:

  1. ~# cp /media/mmcblk0p1/imx6dl-colibri-rcpkd_pb.dtb \
  2. >    /tmp/imx6dl-colibri-rcpkd_pb.dtb
  3. ~# python c_pwm.py
  4. OK: ustawiono 500 Hz
  5. ~# ^C
  6. ~# dd if=/tmp/imx6dl-colibri-rcpkd_pb.dtb bs=1 skip=45332 count=24 2>/dev/null | hexdump -C
  7. 00000000  00 00 00 63 00 00 00 00  00 1e 84 80 00 00 00 01  |...c............|
  8. 00000010  00 00 00 03 00 00 00 20                           |....... |
  9. 00000018

Jeszcze można tak:

  1. ~# hexdump -C /tmp/imx6dl-colibri-rcpkd_pb.dtb | grep "00 00 00 00 1e 84 80"
  2. 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:

  1. ~# cp /tmp/imx6dl-colibri-rcpkd_pb.dtb \
  2.    /media/mmcblk0p1/imx6dl-colibri-rcpkd_pb.dtb
  3. ~# sync
  4. ~# reboot

Po restarcie należy zweryfikować czy wszystko poszło dobrze:

  1. ~# cat /sys/kernel/debug/pwm
  2. platform/2088000.pwm, 1 PWM device
  3. 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ą.