Randomness on nRF52 using Embedded Swift
Introduction
Randomness comes in handy in many different places. It’s useful in the logic of many games to make enemies behave in an unpredictable fashion, it can distribute the timing of events when used within a retry mechanism and random data is sometimes integral to AI algorithms.
In this post, we’ll see how to generate random numbers in Embedded Swift on an nRF52840 DK using the nRF Connect SDK.
Reading documentation
The External dependencies section of the Embedded Swift — User Manual lists some external dependencies that need to be present at link-time, as the Swift standard library or Embedded Swift runtime might need to call them.
Of particular interest to us is:
- using Hashable, Set, Dictionary, or random-number generating APIs.
- dependency:
void arc4random_buf(void *, size_t);
Testing the code
Let’s write some basic code to print a random digit every 5 seconds and try to compile that.
@main
struct Main {
static func main() {
while true {
let randomDigit = Int.random(in: 0...9)
print("Digit: \(randomDigit)")
k_msleep(5000)
}
}
}
From the documentation above, the linker should report an undefined symbol: arc4random_buf
. However, the error message we receive is slightly different.
/opt/nordic/ncs/toolchains/f8037e9b83/opt/zephyr-sdk/arm-zephyr-eabi/bin/../lib/gcc/arm-zephyr-eabi/12.2.0/../../../../arm-zephyr-eabi/bin/ld.bfd: /opt/nordic/ncs/toolchains/f8037e9b83/opt/zephyr-sdk/arm-zephyr-eabi/arm-zephyr-eabi/lib/thumb/v7e-m/nofp/libc_nano.a(lib_a-arc4random.o): in function `_rs_stir_if_needed':
arc4random.c:(.text._rs_stir_if_needed+0x16): undefined reference to `getentropy'
indicating that the getentropy
symbol is undefined.
C standard library
nRF Connect SDK (Zephyr) provides, by default, an implementation of the C standard library. In fact, it provides multiple.
We’re using newlib in our project. This is configured in prj.conf with the parameter
CONFIG_NEWLIB_LIBC=y
This is the configuration used in the nrfx-blink-sdk sample from Apple.
Looking at the source code of newlib, we find in file newlib/libc/stdlib/arc4random.c, the arc4random_buf()function that the Embedded Swift documentation instructs us to implement.
Following the code logic, we eventually find a call to getentropy(), the undefined symbol that our linker error mentioned.
As newlib does not provide an implementation of this function, it is up to us to do so.
Bridging the gap
Let’s start by researching the intended purpose of this function. The getentropy(3) - Linux manual page gives us the answer
fill a buffer with random bytes
Does Zephyr provide functionality to support this implementation ?
Looking through the documentation, we find a section on Random Number Generation.
This section indicates that indeed, Zephyr provides multiple possibilities for generating random numbers. Clicking through to the Random Function APIs documentation, we find the sys_rand_get() function that provides the functionality we’re looking for.
With this, we can glue everything together by implementing the required getentropy()
function in the Stubs.c
file.
int getentropy(void *buffer, size_t length) {
sys_rand_get(buffer, length);
return 0;
}
We also make sure we’re telling Zephyr to use one of the available random number generators by setting the proper configuration option in prj.conf
CONFIG_TEST_RANDOM_GENERATOR=y
Let’s build and flash what we have so far. Connecting to the board, we can see random digits being generated
*** Booting nRF Connect SDK v2.7.0-5cb85570ca43 ***
*** Using Zephyr OS v3.6.99-100befc70c74 ***
Digit: 4
Digit: 9
Digit: 9
Digit: 4
Better randomness
Although the values printed out appear random, the documentation for our CONFIG_TEST_RANDOM_GENERATOR
parameter indicates that
For testing, this option allows a non-random number generator to be used and permits random number APIs to return values that are not truly random.
To improve this, let’s adjust our prj.conf file and select another generator
CONFIG_ENTROPY_DEVICE_RANDOM_GENERATOR=y
With this change, our project fails to build
/opt/nordic/ncs/toolchains/f8037e9b83/opt/zephyr-sdk/arm-zephyr-eabi/bin/../lib/gcc/arm-zephyr-eabi/12.2.0/../../../../arm-zephyr-eabi/bin/ld.bfd: app/libapp.a(Stubs.c.obj): in function `sys_rand_get':
/.../EmbeddedSwift-nRF52-Examples/Random/build/zephyr/include/generated/syscalls/random.h:37: undefined reference to `z_impl_sys_rand_get'
Taking a closer look at the
CONFIG_ENTROPY_DEVICE_RANDOM_GENERATOR documentation, it states that it uses the enabled hardware entropy gathering driver to generate random numbers.
It’s not explicit but reading between the lines, we can deduct that we should enable the entropy driver, which is done by adding this configuration option.
CONFIG_ENTROPY_GENERATOR=y
And with this, our project builds again and we can flash it to our board to test.
Conclusion
In this post, I’ve shown you how to generate random numbers in Embedded Swift when using the nRF Connect SDK. The code is available on GitHub.
In our test program, we used Int.random(in:)
to generate a random integer. But of course we can also use other randomness-based functions, like
let array = ["Jobs", "Wozniak"]
let element = array.randomElement()
And as indicated in the Embedded Swift — User Manual, you can now also use dictionaries and sets
let dictionary = [1: "Jobs", 2: "Wozniak"]
let set : Set<Int> = [1, 2, 3, 3, 2]
Dictionaries ?
let dictionary = ["name": "Jobs"]
Really ?
Main.swift.obj:(.text.$ss7UnicodeO14_NFDNormalizerV7_resume9consumingAB6ScalarV6scalar_AB9_NormDataV04normH0tSgAHSgyXE_tF+0x1d4): undefined reference to `_swift_stdlib_getNormData'
Well that’s another problem not related to random number generation that we’ll address in a future post.