Quadcopter part 6: PWM

[Update] This was on kernel version 3.8.  It had to be completely redone when I performed an upstep to version 4.4.  I will put my findings in a separate post.

I’ve been working on this for a very long time. It was the first time I came into contact with Device Trees in the Linux kernel, and that was a steep learning curve all in its own right, but I also came across a lot technical difficulties and bugs in the beaglebone pwm driver code. Those definitely didn’t help. Here’s the story of enabling 4 PWM pins to do what I want…

Verifying that the PWM output can drive the Turnigy ESCs

The first experiment I wanted to do was to check whether the beaglebone PWM outputs can drive the Turnigy speed controllers. My worry was that the 3.3V PWM signal wouldn’t be enough to drive the 5V controllers.
So I used this guide to control a single (note: single, this will be important later…) PWM output. It was easy to set the period and duty cycle, I verified the timings on the oscilloscope, looking good. I hooked it up to a speed controller, and sure enough: nothing happened. The engine kept beeping happily (it’s an alarm indicating invalid input signal).

Before looking up and ordering a logic level shifter, or designing a transistor inverter circuit to boost the 3.3V to a 5V signal, I decided to give it a try with an Arduino board I had laying around. The Arduino has 5V PWM outputs. Hooking up the beaglebone, the Arduino and the ESCs, I thought it was a good idea to also connnect the grounds for all the devices together, just in case. It worked, the propeller spinning happily on all four engines. I corrected the direction of those propellors that were spinning backwards. In a last attempt I decided to give the beaglebone board just one more try, now that the grounds are all connected together. And that worked too!! Lesson learned: connect ground! Second lesson learned: don’t leave the propeller on the engine when doing tests, I had one of those props fly up to my face when I accidentally set the signal to full power. A quick calculation about the speed that prop reached at full rotational speed, encouraged me to be more careful (I would be stupid to ignore a sharp plastic blade flinging itself around at 500km/h).

Device tree files

To activate the PWM outputs, so that they can be controller from a C++ application, I needed to get to know the device tree overlays. Since the 3.8 kernel Torvalds disallowed the use of platform support code that was quickly flooding the kernel kernel sources. Device trees were to be used instead.
A device tree is a flat file detailing the entire hardware, were all the peripherals are located, which drivers to load, etc, etc…

In order to have some flexibility they are using device tree overlays that can be added on top of a base configuration.

In case of the beaglebone black board, these is a cape manager that can load these device tree overlays at runtime, and the debian image I installed on the beaglebone comes equipped with a full set of example precompiled overlays in /lib/firmware.

In order to load the PWM pins for the quadcopter we need to:
root@beaglebone:/lib/firmware# echo am33xx_pwm > /sys/devices/bone_capemgr.9/slots
root@beaglebone:/lib/firmware# echo bone_pwm_P8_13 > /sys/devices/bone_capemgr.9/slots
root@beaglebone:/lib/firmware# echo bone_pwm_P8_19 > /sys/devices/bone_capemgr.9/slots
root@beaglebone:/lib/firmware# echo bone_pwm_P9_14 > /sys/devices/bone_capemgr.9/slots
root@beaglebone:/lib/firmware# echo bone_pwm_P9_16 > /sys/devices/bone_capemgr.9/slots

and verify with:
cat /sys/devices/bone_capemgr.9/slots
Should return:
0: 54:PF---
1: 55:PF---
2: 56:PF---
3: 57:PF---
4: ff:P-O-L Bone-LT-eMMC-2G,00A0,Texas Instrument,BB-BONE-EMMC-2G
5: ff:P-O-L Bone-Black-HDMI,00A0,Texas Instrument,BB-BONELT-HDMI
8: ff:P-O-L Override Board Name,00A0,Override Manuf,am33xx_pwm
9: ff:P-O-L Override Board Name,00A0,Override Manuf,bone_pwm_P8_13
10: ff:P-O-L Override Board Name,00A0,Override Manuf,bone_pwm_P8_19
11: ff:P-O-L Override Board Name,00A0,Override Manuf,bone_pwm_P9_14
12: ff:P-O-L Override Board Name,00A0,Override Manuf,bone_pwm_P9_16

Or do automatic at bootup by modifying /boot/uboot/uEnv.txt
Add this to the bootargs (in my example I edited a line in the “Example” section):
cape_enable=capemgr.enable_partno=am33xx_pwm,bone_pwm_P8_13,bone_pwm_P8_19,bone_pwm_P9_14,bone_pwm_P9_16

Running into problems

In part 7 I’m explaining how to control the PWM outputs from a C++ application. But during this development I quickly ran into issues. I could control the duty cycle just fine, but the period was always set to 500µs, and couldn’t be changed because INVALID PARAM… It worked just fine with the python script just before.

I checked the code behind the python library, and compared it to the code in the BlackLic C++ library, but the implementation was the same. The pins were controlled by writing to set of files in /sys/devices/ocp3/pwm_test_P8_13.14/. Trying to write 20000000 into the period file will keep failing.

Hunting around the web I quickly found other people encountering the same issue. The PWM on the beaglebone is implemented on three distinct chips. Each of those chips have 2 ehrpwm (enhanced resolution) outputs. The period can only be managed on a per chip basis, and has to be the same for both outputs. However, the beaglebone pwm_test driver exposes both outputs separately, and asserts that any new configuration you want to apply to an output has to be same as the other (chicken or egg?). Maybe it is possible to disable both outputs, change the periods, and enable them again, I haven’t looked into that in greater detail.

The prevailing solution on the web would be to patch the pwm_test driver. The period would be set to 0 in the device tree overlay, and the test_pwm driver would interpret this value and not enable the output. That would allow you to change the period after boot. In my case I wasn’t really interested in being able to change the period, I just wanted it set to 20ms. And if the device tree overlays would allow me to do that, I wouldn’t even need the patch.
Source: http://saadahmad.ca/using-pwm-on-the-beaglebone-black/

I found the device tree source files here: https://github.com/beagleboard/devicetree-source
But I discovered later that I could have also decompiled the precompiled dtbo files in the /lib/firmware folder.

I went ahead and changed the period in these files from 500000 to 20000000 (20ms):
root@beaglebone:~/dts# vim bone_pwm_P9_14-00A0.dts
root@beaglebone:~/dts# vim bone_pwm_P9_16-00A0.dts
root@beaglebone:~/dts# vim bone_pwm_P8_13-00A0.dts
root@beaglebone:~/dts# vim bone_pwm_P8_19-00A0.dts

Compile like this:
dtc -O dtb -o bone_pwm_P9_14-00A0.dtbo -b 0 -@ bone_pwm_P9_14-00A0.dts
dtc -O dtb -o bone_pwm_P9_16-00A0.dtbo -b 0 -@ bone_pwm_P9_16-00A0.dts
dtc -O dtb -o bone_pwm_P8_13-00A0.dtbo -b 0 -@ bone_pwm_P8_13-00A0.dts
dtc -O dtb -o bone_pwm_P8_19-00A0.dtbo -b 0 -@ bone_pwm_P8_19-00A0.dts

Copy to the dtbo files, but make sure you backup /lib/firmware first!
cp *.dtbo /lib/firmware

Rebooting the beaglebone, and still not working…. The period was still 500µs.

It took me a whole while before I figured out what was actually happening. During the search I decided to download and recompiled the kernel, just like the original article suggested. However the howto on rebuilding the kernel is not correct for the kernel image in the standard debian image, instead:
git clone https://github.com/RobertCNelson/bb-kernel.git
cd bb-kernel
git checkout 3.8.13-bone50
sudo apt-get install device-tree-compiler lzma lzop u-boot-tools libncurses5-dev:amd64 libncurses5:i38
./build_kernel.sh

In the folder KERNEL/firmware/capes I could find the same dts files and looking through the kernel code, it seemed like these device tree files are compiled and included in a binary blob somewhere in the kernel binary, instead of being read from /lib/firmware like everyone claimed it was.

Anyway…
I shared the bb-kernel folder over samba, so I could mount it on the beaglebone:
mkdir bb-kernel
mount -t cifs //192.168.1.102/bb-kernel ./bb-kernel -o user=nobody

I also had to change a few commands in bb-kernel/tools/local_install.sh:
where you see:
sudo tar xf "${DIR}/deploy/${KERNEL_UTS}-dtbs.tar.gz" -C "${location}/dtbs/"
replace it with:
sudo tar xf "${DIR}/deploy/${KERNEL_UTS}-dtbs.tar.gz" --no-same-owner -C "${location}/dtbs/"

Now run ./tools/local_install.sh on the beaglebone and the new kernel with the adapted device tree files will be installed on the beaglebone.

That did replace the modules folder, and made me lose my mt7601Usta driver for the wireless antenna. Instead of doing the complete local_install, I just copied the zImage I found the bb-kernel/deploy folder over the /boot/uboot/zImage in a clean debian installation. Reboot, and it worked!

Second thought… maybe you are supposed to copy the dts files into a file of your own, adapt as needed and try to push that into /lib/firmware. So I did a few more experiments:

  • Increase the version number (from 00A0 to 00A1), and hope that the system is intelligent enough to load the one with the highest version. It wasn’t
  • Renaming the file, and using the new name. I renamed bone_pwm_P8_13-00A0.dts to panic1_pwm_P8_13-00A0.dts, compiled it, put it in the /lib/firmware folder. And it does work when you echo the new name into the slots file, but the bootloader doesn’t seem to be able to access the files in /lib/firmware, putting the new name in the uEnv.txt file didn’t work.

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.