In the previous tutorials, we set up RAIL to do various tasks through API commands. However, RAIL can inform the application on various events almost real-time (basically, wiring interrupts through RAIL). So let’s improve what we had in the previous tutorial.
The Event Handler Function
With the RAIL_Init API, we passed a pointer of the event handler function (usually called RAILCb_Generic):
The events variable is a bit field, holding all possible events. Be careful, this bit field does not fit into a 32-bit variable.
The event handler should be prepared for multiple events in a single call. There’s no “clearFlag” mechanism, each event will be signaled once, and the application is responsible for handling it.
Keep in mind that almost all events are generated from interrupts, so the event handler is usually running from interrupt context (i.e., interrupts are disabled). Therefore, the event handler should run fast; if you have to do something that takes more time, set a flag, and do it from the main loop.
Configuring Events
By default, all events are disabled. They can be enabled/disabled at runtime using RAIL_ConfigEvents().
It has a mask and an events parameter, making it simple to configure multiple events at the same time; e.g., to enable a single event, leaving the others untouched:
If you enable/disable events in group, it’s a good practice to create a similar group of events in your application.
Tx Error Handling
In the last tutorial, we sent out a frame, but we didn’t check the results because it would have needed the event handler. Let’s turn on led0 on success, and print errors to the serial.
First, we’ll need to enable the events. We can do that during init, since we don’t really need to disable anything at runtime for such simple applications:
RAIL_Status_t status = RAIL_StartRx(railHandle, 0, NULL);
if ( status != RAIL_STATUS_NO_ERROR ){
BSP_LedToggle(1);
}
This is similar to Tx but this time, the only important option is the second one, which is the channel.
Finally, let’s check the events for success and errors:
void RAILCb_Generic(RAIL_Handle_t railHandle, RAIL_Events_t events)
{
if ( events & RAIL_EVENTS_RX_COMPLETION ){
if ( events & RAIL_EVENT_RX_PACKET_RECEIVED ){
BSP_LedToggle(0);
} else {
BSP_LedToggle(1); //all other events in RAIL_EVENTS_RX_COMPLETION are errors
}
}
}
Conclusion
Now we have a transmitter and a receiver code, but we’re not actually downloading messages. Next time, we’re going to combine the two and print it to the UART console.
In this tutorial, we’re going to modify the project Simple RAIL with HAL to transmit a packet. Let’s use the default, fixed length 16Byte payload configuration first (assuming you are familiar with using RAIL Test sample application in Flex SDK).
Buffer Handling
Setting Up the Packet Buffer
RAIL requires a buffer (usually called FIFO) to be configured for Tx (this is slightly different from RAIL 1.x, where the library initialized that buffer automatically). First, we have to allocate some memory for that buffer:
Note that you can’t use any arbitrary size:; EFR32 creates a FIFO ring buffer on this memory area, and it can only handle buffer sizes of a power of 2 between 64 and 4096 (i.e., 64, 128, 256, 512, 1024, 2048, or 4096).
To load the payload of the packet to the buffer, we have two options: write it via RAIL APIs or write to the memory directly.
The memcpy is just a standard C instruction to write to buffers, and we use RAIL_SetTxFifo to pass that buffer to RAIL. We also tell RAIL how long the buffer is, and how much data it has already in it.
In this case, we pass the buffer to RAIL when it’s still empty, and we write to that buffer using a RAIL API. Note that using memcpy instead of WriteTxFifo would not work: while it would write the memory itself, it wouldn’t change the read and write pointers of the FIFO, handled by RAIL, so RAIL would still think the buffer is empty.
Direct or Indirect
There are two main factors that could decide which method to use to write the FIFO:
With indirect mode, the buffer can be used for partial packets and simpler to use for multiple packets.
Calling RAIL_SetTxFifo does not move any memory, just sets a few register, while calling RAIL_WriteTxFifo does need some time to copy the payload to the buffer.
So in most cases, RAIL_SetTxFifo is better and simpler. If you want to send the same packet over and over (or only change a few bytes in it), it’s much better, since you don’t have to write the whole packet again.
On the other hand, if you want to send longer packets than your buffer, you must use RAIL_WriteTxFifo. It’s also useful if you want to send out a lot of packets (e.g., a segmented message, or a single WriteTxFifo for each packet).
FIFO Reset
The last parameter of RAIL_WriteTxFifo can reset the FIFO, which means it will invalidate the data already in there. This is also possible with RAIL_ResetFifo. I would generally recommend not to use it, a well-written code shouldn’t need it (as it should only store in the FIFO what should be sent out), and it takes extra time.
This instructs RAIL to start transmitting the data stored in the FIFO on channel 0 (second parameter). The third parameter can change various options, like using the second configured sync word (see RAIL API reference). The last parameter is only required for DMP (dynamic multiprotocol) mode.
Changing the Packet Length
You can change the configured fixed length on the Radio Configurator, but that’s obviously not possible at runtime, which is often needed. Note that the amount of data loaded into the FIFO does not matter as long as it’s equal to or greater than the length of the frame.
Changing Packet Length with a Fixed Length Configuration
With the API RAIL_SetFixedLength(), it’s possible to change the pre-configured length (stored in rail_config.c) at runtime. Note that this changes the length of the packets on the Rx side as well. If you want to return to the pre-configured value, you can use RAIL_SetFixedLength(railHandle, RAIL_SETFIXEDLENGTH_INVALID).
Using Variable Length Configurations
A lot of protocols store the length of the packet in the header of the frame. For example, in 802.15.4, the first byte stores the length (and the maximum is 127). To set this up, use the following settings in the Radio Configurator:
Set Frame Length Algorithm to VARIABLE_LENGTH
Enable the Header
Set the length of the header to 1, and enable CRC over it
Set variable length bit size to 8
Set the maximum length to 127
For more information on this setup, and on more advanced variable length configs, see AN971.
With this setup, the radio will always be in variable length mode. This means that:
During Rx, RAIL decodes the length field and set up the frame accordingly. Invalid length frames will be dropped
During Tx, RAIL does the same
RAIL_SetFixedLength() is not available
This means that during Tx, we have to make sure that the committed length in the header matches the amount of bytes you load into the FIFO. It also means that if we set the length field to more than 127, we will get a transmit error.
This would be a valid, 16B frame:
RAIL is short for Radio Abstraction Interface Layer, and is the most direct interface available for EFR32 radios. You can think of it as a radio driver. While this sounds like a restriction, this actually makes software development for proprietary wireless much simpler:
EFR32 has a very flexible radio, which also makes it quite complex. RAIL (and the radio configurator) provides an easy-to-use interface of EFR32's features.
The various generations of the different EFR32s are slightly different, but the RAIL API is the same (except maybe the chip specific updates), which makes hardware updates almost seamless.
We plan to add RAIL support to all of our future radio MCUs.
RAIL application development is currently possible in the Flex SDK in Simplicity Studio. To progress further with this tutorial, please remember to open the RAIL API documentation.
When to Use RAIL?
If you have an existing protocol, and you must be compatible with it, you'd probably have to use RAIL (as all of our stacks use a standardized frame format). If you want to use sub-GHz communication, you can use RAIL or Connect (and Z-Wave, in the near future), but for simple point to point communication, it's probably simpler to use RAIL.
What is covered by RAIL?
RAIL only supports what the radio hardware supports. For example, auto ACK and address filter is available, but FHSS is not. It can be written quite easily in application, but we (currently) have no hardware acceleration for it. While security is important for wireless communication, it's also not a task for RAIL. The crypto engine is an independent peripheral that can be used to encrypt payloads before giving them to RAIL and the radio hardware.
RAIL Dependencies
To link the RAIL library, you'll need the following emlibs:
em_cmu.c
em_core.c
em_sytem.c
em_emu.c (not a direct requirement but a requirement for em_cmu.c)
Before initializing RAIL, you have to initialize a few things first:
DCDC converter should be initialized for the hardware
HFXO clock should be running and should be used as the main oscillator
RAIL's timer can be set up to work seamlessly in EM2, even though it uses the HFPER clock tree. For that, you'll also need the following:
LFRCO or LFXO running
PRS clock running, PRS channel 7 left unused
On EFR32xG1 and EFR32xG12, RTCC running, and compare channel 0 left unused
Supported Energy Modes
EM1 or higher is required for the radio to be operational (i.e., Transmitting, receiving, or waiting for packets).
EM2 or higher is required for RAIL scheduling or timers (This needs configuration, a topic in Tutorial 5).
EM3 or higher is required for RAIL to work without re-initialization.
Writing Code from Scratch
We do not recommend writing code from scratch because it is much simpler to have something existing that sets up the include paths and linker settings correctly. The best way to start is the example Simple RAIL with HAL. In the next chapter, we'll go through the init code of that project.
RAIL Initialization
RAIL requires some peripherals to work. The simplest way to set them up is to use the RAIL-hal plugin, and call halInit(). If you don't want to use the RAIL-hal plugin, see how the Simple RAIL without HAL example initializes the peripherals.
This will initialize RAIL, set up the railHandle to be used in almost all APIs and configures the event handler function. Note that railCfg is not allocated in stack: It has a member (RAIL_StateBuffer_t buffer) that's used by RAIL to store state variables.
Once RAIL is running, we'll load the configuration we generated: RAIL_ConfigChannels(railHandle, channelConfigs[0], NULL);
Finally, we set up the PA (Power Amplifier, required for transmissions):
RAIL_TxPowerConfig_t txPowerConfig = {
#if HAL_PA_2P4_LOWPOWER
.mode = RAIL_TX_POWER_MODE_2P4_LP,
#else
.mode = RAIL_TX_POWER_MODE_2P4_HP,
#endif
.voltage = BSP_PA_VOLTAGE,
.rampTime = HAL_PA_RAMP,
};
if (channelConfigs[0]->configs[0].baseFrequency < 1000000000UL) {
// Use the Sub-GHz PA if required
txPowerConfig.mode = RAIL_TX_POWER_MODE_SUBGIG;
}
RAIL_ConfigTxPower(railHandle, &txPowerConfig);
RAIL_SetTxPower(railHandle, HAL_PA_POWER);
Note the BSP_ and HAL_ macros: those are generated by the Hardware Configurator (see AN1115 for details). Also, there are simpler ways to select the mode (i.e., you don't have to check the configuration if you only ever need the Sub-GHz mode).
Actually, there are only 2 API calls to be noticed above after a long preparation:
The first configures the PA, the second sets up the output power of the device.
The project also enables calibrations, however, it's a bit more difficult to use them, so we'll return to that topic in its own tutorial.
Conclusion
This project is ready to send out frames. We're going to do that in the next part. You can also find other tutorials from the table of content site.
Please remember to check out the Silicon Labs RAIL API Reference Guide in Studio, for a detailed description on the functions and types discussed in this chapter!
Proprietary Knowledge Base
RAIL Tutorial 3: Event Handling
This tutorial builds on the following tutorials:
Please read them first if you haven't.
You can find other tutorials on the Table of Contents site.
In the previous tutorials, we set up RAIL to do various tasks through API commands. However, RAIL can inform the application on various events almost real-time (basically, wiring interrupts through RAIL). So let’s improve what we had in the previous tutorial.
The Event Handler Function
With the
RAIL_Init
API, we passed a pointer of the event handler function (usually calledRAILCb_Generic
):The
events
variable is a bit field, holding all possible events. Be careful, this bit field does not fit into a 32-bit variable.The event handler should be prepared for multiple events in a single call. There’s no “clearFlag” mechanism, each event will be signaled once, and the application is responsible for handling it.
Keep in mind that almost all events are generated from interrupts, so the event handler is usually running from interrupt context (i.e., interrupts are disabled). Therefore, the event handler should run fast; if you have to do something that takes more time, set a flag, and do it from the main loop.
Configuring Events
By default, all events are disabled. They can be enabled/disabled at runtime using
RAIL_ConfigEvents()
.It has a
mask
and anevents
parameter, making it simple to configure multiple events at the same time; e.g., to enable a single event, leaving the others untouched:To disable it, leaving the other events untouched:
RAIL also defines a few event groups:
RAIL_EVENTS_ALL
includes all the eventsRAIL_EVENTS_NONE
includes no events (equivalent to 0)RAIL_EVENTS_TX_COMPLETION
includes all possible events after a successfulStartTx
RAIL_EVENTS_RX_COMPLETION
includes all possible events that can result in state transition from Rx modeThis can be used, for example, to disable all events:
If you enable/disable events in group, it’s a good practice to create a similar group of events in your application.
Tx Error Handling
In the last tutorial, we sent out a frame, but we didn’t check the results because it would have needed the event handler. Let’s turn on led0 on success, and print errors to the serial.
First, we’ll need to enable the events. We can do that during init, since we don’t really need to disable anything at runtime for such simple applications:
Next, let's check for errors returned by the startTx call:
Finally, let's check the events for success and errors:Receiving Packets
Let’s create another application, but this time, we’re going to receive packets (as usual, start from Simple RAIL with HAL).
Again, we should start with event configuration:
Next, let's switch the radio to Rx mode:
This is similar to Tx but this time, the only important option is the second one, which is the channel.
Finally, let’s check the events for success and errors:
Conclusion
Now we have a transmitter and a receiver code, but we’re not actually downloading messages. Next time, we’re going to combine the two and print it to the UART console.
API Introduced in this Tutorial
Functions
Types and enums
RAIL Tutorial 2: Transmitting a Packet
This tutorial builds on the following tutorial/s:
Please read them first if you haven't.
You can find other tutorials on the Table of Contents site.
In this tutorial, we’re going to modify the project Simple RAIL with HAL to transmit a packet. Let’s use the default, fixed length 16Byte payload configuration first (assuming you are familiar with using RAIL Test sample application in Flex SDK).
Buffer Handling
Setting Up the Packet Buffer
RAIL requires a buffer (usually called FIFO) to be configured for Tx (this is slightly different from RAIL 1.x, where the library initialized that buffer automatically). First, we have to allocate some memory for that buffer:
Note that you can’t use any arbitrary size:; EFR32 creates a FIFO ring buffer on this memory area, and it can only handle buffer sizes of a power of 2 between 64 and 4096 (i.e., 64, 128, 256, 512, 1024, 2048, or 4096).
To load the payload of the packet to the buffer, we have two options: write it via RAIL APIs or write to the memory directly.
Writing to the Buffer Directly
The memcpy is just a standard C instruction to write to buffers, and we use
RAIL_SetTxFifo
to pass that buffer to RAIL. We also tell RAIL how long the buffer is, and how much data it has already in it.Writing to the Buffer Indirectly
In this case, we pass the buffer to RAIL when it’s still empty, and we write to that buffer using a RAIL API. Note that using memcpy instead of WriteTxFifo would not work: while it would write the memory itself, it wouldn’t change the read and write pointers of the FIFO, handled by RAIL, so RAIL would still think the buffer is empty.
Direct or Indirect
There are two main factors that could decide which method to use to write the FIFO:
RAIL_SetTxFifo
does not move any memory, just sets a few register, while callingRAIL_WriteTxFifo
does need some time to copy the payload to the buffer.So in most cases,
RAIL_SetTxFifo
is better and simpler. If you want to send the same packet over and over (or only change a few bytes in it), it’s much better, since you don’t have to write the whole packet again.On the other hand, if you want to send longer packets than your buffer, you must use
RAIL_WriteTxFifo
. It’s also useful if you want to send out a lot of packets (e.g., a segmented message, or a single WriteTxFifo for each packet).FIFO Reset
The last parameter of
RAIL_WriteTxFifo
can reset the FIFO, which means it will invalidate the data already in there. This is also possible withRAIL_ResetFifo
. I would generally recommend not to use it, a well-written code shouldn’t need it (as it should only store in the FIFO what should be sent out), and it takes extra time.Transmitting the packet
Starting the transmission is very simple:
This instructs RAIL to start transmitting the data stored in the FIFO on channel 0 (second parameter). The third parameter can change various options, like using the second configured sync word (see RAIL API reference). The last parameter is only required for DMP (dynamic multiprotocol) mode.
Changing the Packet Length
You can change the configured fixed length on the Radio Configurator, but that’s obviously not possible at runtime, which is often needed. Note that the amount of data loaded into the FIFO does not matter as long as it’s equal to or greater than the length of the frame.
Changing Packet Length with a Fixed Length Configuration
With the API
RAIL_SetFixedLength()
, it’s possible to change the pre-configured length (stored in rail_config.c) at runtime. Note that this changes the length of the packets on the Rx side as well. If you want to return to the pre-configured value, you can useRAIL_SetFixedLength(railHandle, RAIL_SETFIXEDLENGTH_INVALID)
.Using Variable Length Configurations
A lot of protocols store the length of the packet in the header of the frame. For example, in 802.15.4, the first byte stores the length (and the maximum is 127). To set this up, use the following settings in the Radio Configurator:
For more information on this setup, and on more advanced variable length configs, see AN971.
With this setup, the radio will always be in variable length mode. This means that:
RAIL_SetFixedLength()
is not availableThis means that during Tx, we have to make sure that the committed length in the header matches the amount of bytes you load into the FIFO. It also means that if we set the length field to more than 127, we will get a transmit error.
This would be a valid, 16B frame:
We use PAYLOAD_LENGTH-1 in the length field since the 1B length field itself shouldn't be counted.
Conclusion
We didn't care about possible errors generated by RAIL: we do that next time, which also provides hints for receiving packets.
API Introduced in this Tutorial
Functions
RAIL Tutorial 1: Introduction and Initialization
What is RAIL?
RAIL is short for Radio Abstraction Interface Layer, and is the most direct interface available for EFR32 radios. You can think of it as a radio driver. While this sounds like a restriction, this actually makes software development for proprietary wireless much simpler:
See this KBA for more details.
RAIL application development is currently possible in the Flex SDK in Simplicity Studio. To progress further with this tutorial, please remember to open the RAIL API documentation.
When to Use RAIL?
If you have an existing protocol, and you must be compatible with it, you'd probably have to use RAIL (as all of our stacks use a standardized frame format). If you want to use sub-GHz communication, you can use RAIL or Connect (and Z-Wave, in the near future), but for simple point to point communication, it's probably simpler to use RAIL.
What is covered by RAIL?
RAIL only supports what the radio hardware supports. For example, auto ACK and address filter is available, but FHSS is not. It can be written quite easily in application, but we (currently) have no hardware acceleration for it. While security is important for wireless communication, it's also not a task for RAIL. The crypto engine is an independent peripheral that can be used to encrypt payloads before giving them to RAIL and the radio hardware.
RAIL Dependencies
To link the RAIL library, you'll need the following emlibs:
Before initializing RAIL, you have to initialize a few things first:
RAIL's timer can be set up to work seamlessly in EM2, even though it uses the HFPER clock tree. For that, you'll also need the following:
Supported Energy Modes
EM1 or higher is required for the radio to be operational (i.e., Transmitting, receiving, or waiting for packets).
EM2 or higher is required for RAIL scheduling or timers (This needs configuration, a topic in Tutorial 5).
EM3 or higher is required for RAIL to work without re-initialization.
Writing Code from Scratch
We do not recommend writing code from scratch because it is much simpler to have something existing that sets up the include paths and linker settings correctly. The best way to start is the example Simple RAIL with HAL. In the next chapter, we'll go through the init code of that project.
RAIL Initialization
RAIL requires some peripherals to work. The simplest way to set them up is to use the RAIL-hal plugin, and call
halInit()
. If you don't want to use the RAIL-hal plugin, see how the Simple RAIL without HAL example initializes the peripherals.Next, we'll need to initialize RAIL:
This will initialize RAIL, set up the
railHandle
to be used in almost all APIs and configures the event handler function. Note thatrailCfg
is not allocated in stack: It has a member (RAIL_StateBuffer_t buffer
) that's used by RAIL to store state variables.Once RAIL is running, we'll load the configuration we generated:
RAIL_ConfigChannels(railHandle, channelConfigs[0], NULL);
Finally, we set up the PA (Power Amplifier, required for transmissions):
Note the BSP_ and HAL_ macros: those are generated by the Hardware Configurator (see AN1115 for details). Also, there are simpler ways to select the mode (i.e., you don't have to check the configuration if you only ever need the Sub-GHz mode).
Actually, there are only 2 API calls to be noticed above after a long preparation:
The first configures the PA, the second sets up the output power of the device.
The project also enables calibrations, however, it's a bit more difficult to use them, so we'll return to that topic in its own tutorial.
Conclusion
This project is ready to send out frames. We're going to do that in the next part. You can also find other tutorials from the table of content site.
Please remember to check out the Silicon Labs RAIL API Reference Guide in Studio, for a detailed description on the functions and types discussed in this chapter!
API Introduced in this Tutorial
Functions
Types and enums
天线外部匹配网络推荐
有些形式的天线在不用外部匹配电路(例如印刷倒F天线)的情况下就能固有地匹配到目的输入阻抗(典型的单端50欧姆)。然而,电路板的大小,朔料外壳,金属屏蔽罩,和天线附近的元器件都影响天线的性能。为了得到最好的性能,天线可能需要调整,可由两种途径实现:
通常客户设计不愿做PCB Layout更改,因此外部元器件调整是优先选择的做法。为达此目的,Silabs通常建议保留SMD焊盘来放置作为外部调整天线的器件,此时建议的外部天线匹配结构是一个3元Pi型网络。你只要使用Pi型网络中的最多两个元件(一个串联元件和一个并联元件)就可做到一个好的匹配电路。任何未知的无源阻抗都能用此Pi型网络匹配到50欧,因为所有的L, C, L-C, C-L组合都能在此结构上实现,因此任何的失调都能补偿回来。
需要注意的是每个天线设计匹配的实现可能需要不同的电感电容组合。
建议用下面3元Pi型网络来达到天线外部匹配的目的:
宽带PCB印刷天线
问题:
怎样做能使PCB天线频率带宽更宽?
回答:
在有些应用场合下,PCB印制天线的带宽可能不够宽。这篇文章总结了一些怎样使印制天线带宽更宽的设计技巧。
—— 增加PCB尺寸(例如单极天线的地平面)。避免使用比1/4波长更小的射频模块。小模块通常是天线增益低,天线带宽更窄(由于有高Q因子)。
—— 增加PCB板的厚度。当然,它通常由设计决定了的。
—— 减小PCB介质的介电常数。选用低介电常数的PCB板材。
—— 在天线结构中使用更宽的和/或渐变的走线。
—— 在外部天线匹配网络中使用一些技巧。如使用更多的器件做匹配电路;在匹配电路中构建谐振器。也可参考Bode-Fano, Youla匹配技术。
影响RF距离的因素
如下所列的很多因素可影响得到的RF距离:
— 发射功率和发射天线的增益。
— 接收灵敏度和接收天线的增益。
— 频率:它与天线增益和有效面积有关。简单地讲就是链路以较低频率工作可以得到较好的RF距离。
— 天线辐射方向图:接收天线和发射天线互相以他们最大辐射主瓣对准就可得到最好的RF距离。有可能在天线方向图的某些方向上有最小的凹槽,从而使得RF距离在这些方向上很差。
— 干扰和噪声:任何带内噪声对距离都有严重的负面影响,因为它屏蔽了接收端希望接收的信号(参见共道抑制参数)。而且,强的带外噪声也会因接收机的邻道选择性和阻塞性能而恶化RF距离。
— Tx和Rx之间的频率差异:对于载波频率必需正确设置的窄带系统来说这将更加关键。
— 最终产品的放置和封装:天线性能可被天线放置位置和天线附近的材料所影响。为了规避任何失调影响(从而使RF距离恶化),必需确保做好推荐的天线(或模块)位置和间隙。
— 环境:理想情况是一个没有反射(如没有墙壁,大型障碍物,树木,房子等),接收和发射之间有直视LOS,在FRESNEL椭圆区间(参见在线FRESNEL椭圆区间计算器)内没有任何障碍物的室外环境。稍差的情况是市区环境,或者说发射和接收直接没有直视LOS。最差的情况是室内的环境,没有直视LOS,有墙壁,障碍物等引起反射。可以表述环境影响的传播常数在接收和发射之间有直视LOS室外环境典型值是2.5到3.5,而在室内环境甚至可以达到4到6。
— 发射机和接收机的高度:这也和在FRESNEL椭圆区间内是否有障碍物相关,如地面等。如果有障碍物,则对RF距离就会有负面的影响。因此,更高地放置节点(发射机,接收机)就可得到更远的RF距离。