You are here

Electronics and embedded systems

Building a continuous-integration Android build server on FreeBSD: Part two: configuring Gitlab-CI

Not so frequently asked questions and stuff: 

The FreeBSD logoImageImageImage

In this series of blog posts, we're going to create a Android build server for continuous integration on FreeBSD.

  • Part one will explain how to build Android APKs using Gradle on FreeBSD using the Linux emulation.
  • Part two (this post) will explain how to configure Gitlab-CI to be able to run builds automatically for each commit.
  • Part three will explain how to configure Jenkins to be able to run builds and email the APKs to people.

Step 1: Install, configure and register the Gitlab-CI runner

Most of the information of that step comes from the original documentation.

Create a nice user for the runner:

# pw group add -n gitlab-runner
# pw user add -n gitlab-runner -g gitlab-runner -s /usr/local/bin/bash
# mkdir /home/gitlab-runner
# chown gitlab-runner:gitlab-runner /home/gitlab-runner

Download the runner executable (or build it yourself (see the end of the post)), and mark it as such:

# fetch -o /usr/local/bin/gitlab-ci-multi-runner https://gitlab-ci-multi-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-ci-multi-runner-freebsd-amd64
# chmod +x /usr/local/bin/gitlab-ci-multi-runner

Create and chmod the log file:

# touch /var/log/gitlab_runner.log && sudo chown gitlab-runner:gitlab-runner /var/log/gitlab_runner.log

Create the rc-script (/usr/local/etc/rc.d/gitlab_runner):

#!/bin/sh
# PROVIDE: gitlab_runner
# REQUIRE: DAEMON NETWORKING
# BEFORE:
# KEYWORD:

. /etc/rc.subr

name="gitlab_runner"
rcvar="gitlab_runner_enable"

load_rc_config $name

user="gitlab-runner"
user_home="/home/gitlab-runner"
command="/usr/local/bin/gitlab-ci-multi-runner run"
pidfile="/var/run/${name}.pid"

start_cmd="gitlab_runner_start"
stop_cmd="gitlab_runner_stop"
status_cmd="gitlab_runner_status"

gitlab_runner_start()
{
    export USER=${user}
    export HOME=${user_home}
    if checkyesno ${rcvar}; then
        cd ${user_home}
        /usr/sbin/daemon -u ${user} -p ${pidfile} ${command} > /var/log/gitlab_runner.log 2>&1
    fi
}

gitlab_runner_stop()
{
    if [ -f ${pidfile} ]; then
        kill `cat ${pidfile}`
    fi
}

gitlab_runner_status()
{
    if [ ! -f ${pidfile} ] || kill -0 `cat ${pidfile}`; then
        echo "Service ${name} is not running."
    else
        echo "${name} appears to be running."
    fi
}

run_rc_command $1

Set the rc script as being executable:

chmod +x /usr/local/etc/rc.d/gitlab_runner

Register the runner with Gitlab-CI:

# sudo -u gitlab-runner -H /usr/local/bin/gitlab-ci-multi-runner register

Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/ci):
https://gitlab.example.com/ci

Please enter the gitlab-ci token for this runner:
sqzGW7_PgmarZ3FsSsUa

Please enter the gitlab-ci description for this runner:
[androidbuild]: Example Android build machine

Please enter the gitlab-ci tags for this runner (comma separated):
android
Registering runner... succeeded                     runner=sqzGW7_T

Please enter the executor: docker-ssh+machine, docker, docker-ssh, parallels, shell, ssh, virtualbox, docker+machine:
shell

Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!

Enable the runner service and start it:

# sysrc -f /etc/rc.conf "gitlab_runner_enable=YES" 
# service gitlab_runner start

Step 2: add the CI configuration to your Android project

Create a file named .gitlab-ci.yml and configure it to your needs.

before_script:
  - export LD_LIBRARY_PATH="/home/androidbuild/android-sdk-linux/build-tools/23.0.2/lib/"
  - export ANDROID_HOME="/home/androidbuild/android-sdk-linux/"

prod:
  only:
    - master
  script:
    - ./gradlew assembleRelease
  artifacts:
    paths:
      - Example/build/outputs/
    expire_in: 1 week

This example file will trigger a build when branch master is updaded, and it will save the artifacts for one week.

Image

Bonus: compiling the runner ourselves

If there's something I don't like, it's downloading executables from the internet when I can build them myself.
Let's build that Gitlab runner ourselves.

Install golang:

# make -C /usr/ports/lang/go install clean

Create a working go directory structure:

% mkdir -p ~/gowork/src/gitlab.com/gitlab-org/
% cd ~/gowork/src/gitlab.com/gitlab-org/
% setenv GOPATH ~/gowork
% setenv PATH "${PATH}:${GOPATH}/bin"

Get the code from Gitlab (checkout the version you want to build):

% git clone https://gitlab.com/gitlab-org/gitlab-ci-multi-runner.git
% cd gitlab-ci-multi-runner/
% git checkout tags/v1.5.2

Build:

% gmake deps docker
% gmake build_simple
% file "out/binaries/gitlab-ci-multi-runner"
out/binaries/gitlab-ci-multi-runner: ELF 64-bit LSB executable, x86-64, version 1 (FreeBSD), statically linked, not stripped

Install (as root):

# install -m 755 out/binaries/gitlab-ci-multi-runner /usr/local/bin/gitlab-ci-multi-runner

Done.

How to stop the watchdog timer of a BeableBone Black running Linux

Not so frequently asked questions and stuff: 

A Beable Bone Black with an operator setting its watchdog timer

The BeagleBone Black's SoC (AM335x) includes a watchdog timer, that will reset the whole board is it isn't pingged regularly.

Let's see if we can stop that thing running the latest Debian GNU/Linux to date.

# uname -a
Linux beaglebone 4.4.9-ti-r25 #1 SMP Thu May 5 23:08:13 UTC 2016 armv7l GNU/Linux
root@beaglebone:~# cat /etc/debian_version
8.4

Ever since this commit, the OMAP watchdog driver has the magic close feature enabled. This means that closing the timer's device won't stop the timer from ticking. The only way to stop it is to send to it the magic character 'V' (a capital 'v').

# wdctl /dev/watchdog
wdctl: write failed: Invalid argument
Device:        /dev/watchdog
Identity:      OMAP Watchdog [version 0]
Timeout:       120 seconds
Timeleft:      119 seconds
FLAG           DESCRIPTION               STATUS BOOT-STATUS
KEEPALIVEPING  Keep alive ping reply          0           0
MAGICCLOSE     Supports magic close char      0           0
SETTIMEOUT     Set timeout (in seconds)       0           0

This feature is particularly useful if you want the watchdog timer to only be active when a specific application is running, and if you then want it to be stopped when the application is stopped normally.

Unfortunately, the kernel can be configure with a mode called "no way out", which means that even tough the magic close feature of the driver is enabled, it won't be honored at all, and you are doomed to ping your timer until the end of time once you opened the device.

# cat /proc/config.gz | gunzip | grep CONFIG_WATCHDOG_NOWAYOUT
CONFIG_WATCHDOG_NOWAYOUT=y

On a kernel version 3.8, the feature was not enabled:

$ cat /proc/config.gz | gunzip | grep CONFIG_WATCHDOG_NOWAYOUT
# CONFIG_WATCHDOG_NOWAYOUT is not set

So, how do we stop that thing?

Well, you can see in the code of the driver that the default value of the kernel can be overridden by a module param:

static bool nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, bool, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
	"(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");

Edit the boot configuration in /boot/uEnv.txt and add set that parameter to 0 in the cmdline:

cmdline=coherent_pool=1M quiet cape_universal=enable omap_wdt.nowayout=0

Reboot the board, and check that the loaded command line was changed correctly:

# cat /proc/cmdline
console=tty0 console=ttyO0,115200n8 root=/dev/mmcblk0p1 rootfstype=ext4 rootwait coherent_pool=1M quiet cape_universal=enable omap_wdt.nowayout=0

That's it. Now if you send a 'V' to the watchdog right before closing it, it will be stopped.

How to use the CAN bus of a Beagle Bone Black from Erlang

Not so frequently asked questions and stuff: 

Communication through a CAN bus between a Beagle Bone Black and a STM32F302 NucleoImage

Let's try using the CAN bus device of a Beagle Bone Black from Erlang.

Here's some information about the system present on the board:

# uname -a
Linux beaglebone 3.14.40-ti-r62 #1 SMP PREEMPT Thu Apr 30 18:28:06 UTC 2015 armv7l GNU/Linux

# cat /etc/issue
Debian GNU/Linux 8 \n \l

BeagleBoard.org Debian Image 2015-05-01

Testing the CAN driver

Let's create a virtual CAN device:

# modprobe vcan
# ip link add dev vcan0 type vcan
# ifconfig vcan0 up
# ip -details -statistics link show vcan0
5: vcan0:  mtu 16 qdisc noqueue state UNKNOWN mode DEFAULT group default
    link/can  promiscuity 0
    vcan
    RX: bytes  packets  errors  dropped overrun mcast
    0          0        0       0       0       0
    TX: bytes  packets  errors  dropped carrier collsns
    0          0        0       0       0       0

Let's try sending/receiving data on that virtual device:

# cansend vcan0 442#DEADBABE

On another terminal:

# candump vcan0
  vcan0  442   [4]  DE AD BA BE

Using the CAN driver from Erlang

I'll be using the excellent CAN module from Tony Rogvall: https://github.com/tonyrog/can.

Let's create a project from the following rebar.config:

{lib_dirs,["deps"]}.
{sub_dirs, ["rel"]}.
{deps, [
    {lager, ".*", {
        git, "git://github.com/basho/lager.git", "master"}
    },
    {can, ".*", {
        git, "git://github.com/tonyrog/can", "master"}
    }
]}.
{erl_opts, [{parse_transform, lager_transform}]}.

Compile everything and start a shell:

# rebar compile
# erl -pa ebin/ -pa deps/*/ebin
1> lager:start().
12:53:30.268 [info] Application lager started on node nonode@nohost
ok
2> lager:set_loglevel(lager_console_backend, debug).
ok
3> can:start().
12:53:39.905 [info] Application uart started on node nonode@nohost
ok
4> can_sock:start(0, [{device,"vcan0"}]).
12:53:39.963 [debug] Supervisor can_sup started can_router:start_link([]) at pid 
12:53:39.965 [debug] Supervisor can_sup started can_if_sup:start_link() at pid 
12:53:39.966 [info] Application can started on node nonode@nohost

12:53:41.213 [debug] can_router: process , param {can_sock,"vcan0",0} joined.
12:53:41.215 [debug] Supervisor can_sup started can_sock:start_link(0, [{device,"vcan0"}]) at pid 
{ok,}

Driver and device are loaded. Let's try sending something:

5> can:send(42, >).
ok
6> 12:54:42.626 [debug] can_router: broadcast: [ID: 02A LEN:4 DATA:41424344]
12:54:42.627 [debug] broadcast: frame={can_frame,42,4,>,0,0}, send=1

On the dump:

# candump vcan0
  vcan0  02A   [4]  41 42 43 44

Sending data works. Let's try receiving data.

Let's attach the shell pid to the router.

6> can_router:attach().
12:58:06.992 [debug] can_router: process  attached.
ok

Information about the module can be queried:

can_router:i().
Interfaces
 1: {can_sock,"vcan0",0}
  input_frames: 3
  output_frames: 2
Applications
:  interface=0
ok

Send something from the terminal:

# cansend vcan0 442#44434241

Do we have a new message?

7> receive V -> V end.
12:58:31.517 [debug] can_router: broadcast: [ID: 442 LEN:4 INTF:1 DATA:44434241]
12:58:31.517 [debug] broadcast: frame={can_frame,1090,4,>,1,-1}, send=1
{can_frame,1090,4,>,1,-1}

Success!

Using the real CAN

Plug your CAN transceiver the following way:

The Beagle Bone Black CAN pinout

Pin Description
24 CAN RX
26 CAN TX

Make sure the dtb is loaded in /boot/uEnv.txt:

dtb=am335x-boneblack-can1.dtb

If not, edit the file and reboot the board.

You can check that the board is aware of its CAN interface:

# cat /sys/kernel/debug/pinctrl/44e10800.pinmux/pinmux-pins | grep -i can
pin 96 (44e10980.0): P9_26_pinmux.53 (GPIO UNCLAIMED) function pinmux_P9_26_can_pin group pinmux_P9_26_can_pin
pin 97 (44e10984.0): P9_24_pinmux.51 (GPIO UNCLAIMED) function pinmux_P9_24_can_pin group pinmux_P9_24_can_pin

Stop the driver, configure the link and start the interface again:

# ifconfig can0 down
# ip link set can0 up type can bitrate 1000000 loopback off triple-sampling on
# if config can0 up

If everything goes right, the link should be working and you can query information about it:

# ip -details -statistics link show can0
3: can0:  mtu 16 qdisc pfifo_fast state UNKNOWN mode DEFAULT group default qlen 10
    link/can  promiscuity 0
    can  state ERROR-ACTIVE (berr-counter tx 0 rx 0) restart-ms 0
          bitrate 1000000 sample-point 0.750
          tq 83 prop-seg 4 phase-seg1 4 phase-seg2 3 sjw 1
          c_can: tseg1 2..16 tseg2 1..8 sjw 1..4 brp 1..1024 brp-inc 1
          clock 24000000
          re-started bus-errors arbit-lost error-warn error-pass bus-off
          0          0          0          1          1          0
    RX: bytes  packets  errors  dropped overrun mcast
    14058      2425     0       89      0       0
    TX: bytes  packets  errors  dropped carrier collsns
    591        591      0       0       0       0

For information, here's how my other device is configured:

//CLOCK CONFIG:

  RCC_OscInitTypeDef RCC_OscInitStruct;
  RCC_ClkInitTypeDef RCC_ClkInitStruct;

  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  HAL_RCC_OscConfig(&RCC_OscInitStruct);

  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_PCLK1;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
  HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2);

  __SYSCFG_CLK_ENABLE();

//CAN CONFIG:

  hcan.Instance = CAN;
  hcan.Init.Prescaler = 2;
  hcan.Init.Mode = CAN_MODE_NORMAL;
  hcan.Init.SJW = CAN_SJW_1TQ;
  hcan.Init.BS1 = CAN_BS1_9TQ;
  hcan.Init.BS2 = CAN_BS2_8TQ;
  hcan.Init.TTCM = DISABLE;
  hcan.Init.ABOM = DISABLE;
  hcan.Init.AWUM = DISABLE;
  hcan.Init.NART = DISABLE;
  hcan.Init.RFLM = DISABLE;
  hcan.Init.TXFP = DISABLE;
  HAL_CAN_Init(&hcan);

(it's a STM32F302).

BeagleBone Black, FreeBSD and GPIOs

Not so frequently asked questions and stuff: 

The LEDs of a BeagleBone Black running FreeBSD are lighten up.The FreeBSD logo

Introduction

The BeagleBone black, using its AM335X CPU, provides a number of GPIOs. Let's try to make them work.

Trying to find the device

dmesg indicates that the kernel found a GPIO device.

gpio0:  mem 0x44e07000-0x44e07fff,0x4804c000-0x4804cfff,0x481ac000-0x481acfff,0x481ae000-0x481aefff irq 96,97,98,99,32,33,62,63 on simplebus0
gpioc0:  on gpio0
gpiobus0:  on gpio0

devinfo and ofwdump confirm that the corresponding nodes exist.

# devinfo
nexus0
  fdtbus0
    simplebus0
      aintc0
      ti_scm0
      am335x_prcm0
      am335x_dmtimer0
      gpio0
        gpioc0
        gpiobus0
      uart0
      ti_edma30
      sdhci_ti0
        mmc0
          mmcsd0
      sdhci_ti1
        mmc1
          mmcsd1
      cpsw0
        miibus0
          smscphy0
      iichb0
        iicbus0
          iic0
          am335x_pmic0
      am335x_pwm0
      am335x_pwm1
      am335x_pwm2
      musbotg0
        usbus0
          uhub0
            ustorage_fs0
        usbus1
          uhub1


# ofwdump -a
Node 0x38:
  Node 0xc4: am335x
    Node 0x124: interrupt-controller@48200000
    Node 0x1b4: scm@44e10000
    Node 0xb24: prcm@44E00000
    Node 0xb88: dmtimers@44E05000
    Node 0xc6c: gpio
    Node 0xd14: serial@44E09000
    Node 0xda8: serial@48022000
    Node 0xe54: serial@48024000
    Node 0xf00: serial@481a6000
    Node 0xfac: serial@481a8000
    Node 0x1058: serial@481aa000
    Node 0x1104: edma3@49000000
    Node 0x1188: mmchs0@48060000
    Node 0x120c: mmchs1@481D8000
    Node 0x12a4: ethernet@4A100000
      Node 0x1340: mdio@0
        Node 0x1388: ethernet-phy@0
    Node 0x13c8: i2c@44e0b000
      Node 0x1454: pmic@24
    Node 0x1494: pwm@48300000
    Node 0x1548: pwm@48302000
    Node 0x15fc: pwm@48304000
    Node 0x16b0: lcd@4830e000
    Node 0x1738: usb@47400000
    Node 0x1808: mbox0@480C8000
    Node 0x1874: spinlock0@480CA000
    Node 0x18c0: pruss@4A300000
  Node 0x1944: aliases
  Node 0x198c: memory
  Node 0x19c4: chosen

# ofwdump -p /am335x/gpio
Node 0xc6c: gpio
  #gpio-cells:
    00 00 00 03
  compatible:
    74 69 2c 67 70 69 6f 00
    'ti,gpio'
  gpio-controller:
  reg:
    44 e0 70 00 00 00 10 00 48 04 c0 00 00 00 10 00 48 1a c0 00
    00 00 10 00 48 1a e0 00 00 00 10 00
  interrupts:
    00 00 00 60 00 00 00 61 00 00 00 62 00 00 00 63 00 00 00 20
    00 00 00 21 00 00 00 3e 00 00 00 3f
  interrupt-parent:
    00 00 00 01

Using the device

Reading the kernel source code indicates that the GPIOs are used with a tool called gpioctl.

Listing the available IOs shows that some of them are already configured.

# gpioctl -f /dev/gpioc0 -l
pin 00: 0       gpio_0
pin 01: 0       gpio_1
pin 02: 0       gpio_2
pin 03: 0       gpio_3
pin 04: 0       gpio_4
pin 05: 0       gpio_5
pin 06: 0       gpio_6
pin 07: 0       gpio_7
pin 08: 0       gpio_8
pin 09: 0       gpio_9
pin 10: 0       gpio_10
pin 11: 0       gpio_11
pin 12: 0       gpio_12
pin 13: 0       gpio_13
pin 14: 0       gpio_14
pin 15: 0       gpio_15
pin 16: 0       gpio_16
pin 17: 0       gpio_17
pin 18: 0       gpio_18
pin 19: 0       gpio_19
pin 20: 0       gpio_20
pin 21: 0       gpio_21
pin 22: 0       gpio_22
pin 23: 0       gpio_23
pin 24: 0       gpio_24
pin 25: 0       gpio_25
pin 26: 0       gpio_26
pin 27: 0       gpio_27
pin 28: 0       gpio_28
pin 29: 0       gpio_29
pin 30: 0       gpio_30
pin 31: 0       gpio_31
pin 32: 0       gpio_32
pin 33: 0       gpio_33
pin 34: 0       gpio_34
pin 35: 0       gpio_35
pin 36: 0       gpio_36
pin 37: 0       gpio_37
pin 38: 0       gpio_38
pin 39: 0       gpio_39
pin 40: 0       gpio_40
pin 41: 0       gpio_41
pin 42: 0       gpio_42
pin 43: 0       gpio_43
pin 44: 0       gpio_44
pin 45: 0       gpio_45
pin 46: 0       gpio_46
pin 47: 0       gpio_47
pin 48: 0       gpio_48
pin 49: 0       gpio_49
pin 50: 0       gpio_50
pin 51: 0       gpio_51
pin 52: 0       gpio_52
pin 53: 0       gpio_53
pin 54: 0       gpio_54
pin 55: 0       gpio_55
pin 56: 0       gpio_56
pin 57: 0       gpio_57
pin 58: 0       gpio_58
pin 59: 0       gpio_59
pin 60: 0       gpio_60
pin 61: 0       gpio_61
pin 62: 0       gpio_62
pin 63: 0       gpio_63
pin 64: 0       gpio_64
pin 65: 0       gpio_65
pin 66: 0       gpio_66
pin 67: 0       gpio_67
pin 68: 0       gpio_68
pin 69: 0       gpio_69
pin 70: 0       gpio_70
pin 71: 0       gpio_71
pin 72: 0       gpio_72
pin 73: 0       gpio_73
pin 74: 0       gpio_74
pin 75: 0       gpio_75
pin 76: 0       gpio_76
pin 77: 0       gpio_77
pin 78: 0       gpio_78
pin 79: 0       gpio_79
pin 80: 0       gpio_80
pin 81: 0       gpio_81
pin 82: 0       gpio_82
pin 83: 0       gpio_83
pin 84: 0       gpio_84
pin 85: 0       gpio_85
pin 86: 0       gpio_86
pin 87: 0       gpio_87
pin 88: 0       gpio_88
pin 89: 0       gpio_89
pin 90: 0       gpio_90
pin 91: 0       gpio_91
pin 92: 0       gpio_92
pin 93: 0       gpio_93
pin 94: 0       gpio_94
pin 95: 0       gpio_95
pin 96: 0       gpio_96
pin 97: 0       gpio_97
pin 98: 0       gpio_98
pin 99: 0       gpio_99
pin 100:        0       gpio_100
pin 101:        0       gpio_101
pin 102:        0       gpio_102
pin 103:        0       gpio_103
pin 104:        0       gpio_104
pin 105:        0       gpio_105
pin 106:        0       gpio_106
pin 107:        0       gpio_107
pin 108:        0       gpio_108
pin 109:        0       gpio_109
pin 110:        0       gpio_110
pin 111:        0       gpio_111
pin 112:        0       gpio_112
pin 113:        0       gpio_113
pin 114:        0       gpio_114
pin 115:        0       gpio_115
pin 116:        0       gpio_116
pin 117:        0       gpio_117
pin 118:        0       gpio_118
pin 119:        0       gpio_119
pin 120:        0       gpio_120
pin 121:        0       gpio_121
pin 122:        0       gpio_122
pin 123:        0       gpio_123
pin 124:        0       gpio_124
pin 125:        0       gpio_125
pin 126:        0       gpio_126
pin 127:        0       gpio_127

Let's try to play with the board's 4 builtin LEDs.

# gpioctl -f /dev/gpioc0 -t 53
# gpioctl -f /dev/gpioc0 -t 54
# gpioctl -f /dev/gpioc0 -t 55
# gpioctl -f /dev/gpioc0 -t 56

Yep, they work.

Let's try configure another random IO.

# gpioctl -f /dev/gpioc0 -c 49 OUT OD
0/OUT
1/OD

GPIO1_17 (P9.23) should be configured as an open-drain output pin. Let's try to change its value.

root@beaglebone:~ # while 1
while? gpioctl -f /dev/gpioc0 -t 49
while? sleep 1
while? end
^C

We created a very slow bit-banged PWM.

Reference and relevant information

BeagleBone Black, FreeBSD and PWMs

Not so frequently asked questions and stuff: 

Two PWM output from a BeagleBone Black running FreeBSDThe FreeBSD logo

Introduction

The BeagleBone black, using its AM335X CPU, provides a number of PWM output. Let's try to make them work.

Trying to find the device

dmesg indicates that the kernel found 3 PWM devices.

am335x_pwm0:  mem 0x48300000-0x483000ff,0x48300100-0x4830017f,0x48300180-0x483001ff,0x48300200-0x4830025f irq 86,58 on simplebus0
am335x_pwm1:  mem 0x48302000-0x483020ff,0x48302100-0x4830217f,0x48302180-0x483021ff,0x48302200-0x4830225f irq 87,59 on simplebus0
am335x_pwm2:  mem 0x48304000-0x483040ff,0x48304100-0x4830417f,0x48304180-0x483041ff,0x48304200-0x4830425f irq 88,60 on simplebus0

devinfo and ofwdump confirm that the corresponding nodes exist.

# devinfo
nexus0
  fdtbus0
    simplebus0
      aintc0
      ti_scm0
      am335x_prcm0
      am335x_dmtimer0
      gpio0
        gpioc0
        gpiobus0
      uart0
      ti_edma30
      sdhci_ti0
        mmc0
          mmcsd0
      sdhci_ti1
        mmc1
          mmcsd1
      cpsw0
        miibus0
          smscphy0
      iichb0
        iicbus0
          iic0
          am335x_pmic0
      am335x_pwm0
      am335x_pwm1
      am335x_pwm2
      musbotg0
        usbus0
          uhub0
            ustorage_fs0
        usbus1
          uhub1


# ofwdump -a
Node 0x38:
  Node 0xc4: am335x
    Node 0x124: interrupt-controller@48200000
    Node 0x1b4: scm@44e10000
    Node 0xb24: prcm@44E00000
    Node 0xb88: dmtimers@44E05000
    Node 0xc6c: gpio
    Node 0xd14: serial@44E09000
    Node 0xda8: serial@48022000
    Node 0xe54: serial@48024000
    Node 0xf00: serial@481a6000
    Node 0xfac: serial@481a8000
    Node 0x1058: serial@481aa000
    Node 0x1104: edma3@49000000
    Node 0x1188: mmchs0@48060000
    Node 0x120c: mmchs1@481D8000
    Node 0x12a4: ethernet@4A100000
      Node 0x1340: mdio@0
        Node 0x1388: ethernet-phy@0
    Node 0x13c8: i2c@44e0b000
      Node 0x1454: pmic@24
    Node 0x1494: pwm@48300000
    Node 0x1548: pwm@48302000
    Node 0x15fc: pwm@48304000
    Node 0x16b0: lcd@4830e000
    Node 0x1738: usb@47400000
    Node 0x1808: mbox0@480C8000
    Node 0x1874: spinlock0@480CA000
    Node 0x18c0: pruss@4A300000
  Node 0x1944: aliases
  Node 0x198c: memory
  Node 0x19c4: chosen

# ofwdump -p /am335x/pwm@48300000
Node 0x1494: pwm@48300000
  compatible:
    74 69 2c 61 6d 33 33 35 78 2d 70 77 6d 00
    'ti,am335x-pwm'
  #address-cells:
    00 00 00 01
  #size-cells:
    00 00 00 01
  reg:
    48 30 00 00 00 00 01 00 48 30 01 00 00 00 00 80 48 30 01 80
    00 00 00 80 48 30 02 00 00 00 00 60
  interrupts:
    00 00 00 56 00 00 00 3a
  interrupt-parent:
    00 00 00 01
  pwm-device-id:
    00 00 00 00

Using the device

There doesn't seem to be much documentation on how to use these devices.

Reading the kernel indicates that the configuration is done using sysctl.

# sysctl -a | grep pwm
"Giant","am335x_pwm softc"
dev.am335x_pwm.0.%desc: AM335x PWM
dev.am335x_pwm.0.%driver: am335x_pwm
dev.am335x_pwm.0.%parent: simplebus0
dev.am335x_pwm.0.period: 1000
dev.am335x_pwm.0.dutyA: 0
dev.am335x_pwm.0.dutyB: 0
dev.am335x_pwm.1.%desc: AM335x PWM
dev.am335x_pwm.1.%driver: am335x_pwm
dev.am335x_pwm.1.%parent: simplebus0
dev.am335x_pwm.1.period: 1000
dev.am335x_pwm.1.dutyA: 0
dev.am335x_pwm.1.dutyB: 0
dev.am335x_pwm.2.%desc: AM335x PWM
dev.am335x_pwm.2.%driver: am335x_pwm
dev.am335x_pwm.2.%parent: simplebus0
dev.am335x_pwm.2.period: 1000
dev.am335x_pwm.2.dutyA: 0
dev.am335x_pwm.2.dutyB: 0

Jackpot! Let's try settings some values in these registers.

# sysctl dev.am335x_pwm.0.period=100
# sysctl dev.am335x_pwm.0.dutyA=50
# sysctl dev.am335x_pwm.0.dutyB=75
# sysctl dev.am335x_pwm.1.period=1000
# sysctl dev.am335x_pwm.1.dutyA=500
# sysctl dev.am335x_pwm.1.dutyB=50
# sysctl dev.am335x_pwm.2.period=250
# sysctl dev.am335x_pwm.2.dutyA=50
# sysctl dev.am335x_pwm.2.dutyB=150

That's it. Probing the pins confirms that the PWM work.

Pin mapping and muxing

Device Pin
sysctl dev.am335x_pwm.1.dutyA P9.14 (ehrpwm1A)
sysctl dev.am335x_pwm.1.dutyB P9.16 (ehrpwm1B)
sysctl dev.am335x_pwm.2.dutyA P8.19 (ehrpwm2A)
sysctl dev.am335x_pwm.2.dutyB P8.13 (ehrpwm2B)

ehrpwm0 is not muxed in the device tree file, so it's not accessible.

Reference and relevant information

nRF24L01 and STM8S microcontrollers

Not so frequently asked questions and stuff: 

Same article in French here.

RF24L01 et STM8S
Two RF24L01 and STM8S.

Introduction

It's very easy to buy on ebay or elsewhere some 2.4 GHz RF transceiver using the Nordic RF24L01 chip, ranging around $3. Let's test a couple of them with two STM8S microcontrollers.

We'll use some dev boards from STM: the STM8SVLDISCOVERY. It includes a STM8S003K3 controller with a USB programmer. That's everything we need to make the stuff work without any soldering.

Getting used to the RF module

Des trames SPI entre un STM8S et un RF42L01.
SPI signals between a STM8S and a RF24L01.

We interact with the RF24L01 using a standard SPI link. (CS/CLK/MISO/MOSI). One GPIO (CE) allows switching between the STANDBY and active SEND/RECEIVE modes. Another one (IRQ) allows us to know when something important happens (packet received, packet sent, packet sent but lost).

The module works with 1 to 32 byte packets. It automatically handles reassembling them on the reception side, and re-emit them when they are lost. It also includes some CRC code to be sure the data is valid. Many other options are available, so read the datasheet to get stars in your eyes.

The module is not full duplex, so you have to alternate between send and receive modes.

Wiring

RF24L01_module_pineout

We'll interface the module as following (see the application note in the datasheet):

Pin Signal Description?
1 (square) GND Ground
2 VCC 3.3V
3 CE Sends data or enable receiving
4 CSN SPI Chip Select
5 SCK SPI Clock
6 MOSI Master data
7 MISO Module Data
8 IRQ Module interruption flag

We connected the two component this way:

STM8S Side 24L01 Side
PC3 CE
PC4 CSN
PC7 MISO
PC6 MOSI
PC5 SCK
PC2 IRQ

Code example

The example code we wrote send two 32 bit integers from the master to the slave. Then the slave effectuates the basic math operations and sends the results.

This kind of example allows you to be sure that the byte stream is reassembled correctly (watch your byte sex is you use another platform), and that the packets are the right ones (i.e. that you get the latest and not the penultimate one or one stored something in a FIFO or a buffer).

The code was written using IAR workbench for STM 1.40.1.

It is available on GitHub here.

License: MIT.

Range testing

We tested the range of the module. We measured a range of about one hundred meters outside. The range inside is obviously less.

Conclusion: for their price, theses modules are great.

Subscribe to Electronics and embedded systems