Development Tools for the EcoBT
Programming the EcoBT can be done using either the official TI-IAR setup or with EPL’s own SDCC setup. The former gives you complete access to the MCU’s functionality, whereas the latter uses all open-source tools but has more restrictions.
1. TI-IAR Setup
Programming the EcoBT can be done using the same setup as TI’s evaluation kit in conjunction with IAR. The requirements are
Package or Accessory | Purpose |
---|---|
CC Debugger | programming the firmware and debugger |
TI’s BLE Stack and software | supporting BLE protocol (master, slave, central, peripheral) |
IAR Embedded Workbench (for Windows) |
compiling and linking user code, OSAL, and BLE stack to form the firmware image |
BTools (for Windows), LightBlue (for iOS), or other BLE Clients |
for testing reading or writing of characteristic values |
The document ”Texas Instruments CC2540/41 Bluetooth Low Energy Software Developer’s Guide” provides a good, detailed explanation on the BLE stack, OSAL overview, workimg with IAR, and sample projects. We recommend that you read it even if you use our SDCC-CBL setup.
2. SDCC-CBL Setup
Programming the EcoBT can also be done using the open-source SDCC in conjunction with our own CBL (compiler binding layer) and programming tools in Python. Developers can write code similar to the IAR source code with some simple modifications. What you need:
Package or Accessory | Purpose |
---|---|
Unix-like environment | (e.g., Cygwin for Windows) to run Gnu make |
BLE USB dongle (e.g., EcoBT Super) | for connecting a host computer to target node via BLE. Windows, Win8; no need for driver on Mac or Linux |
SDCC 3.4.0 | compiling C to 8051 machine code |
Python 2.7.x with pyserial package |
environment for programming tool |
CBL library | for translating calling conventions between IAR and SDCC |
runflash.py script | (part of the zip file above), under sdcc subdirectory for wirelessly programming the node |
Makefile | compilation and linking rules for building the image |
BTools (for Windows), LightBlue (for iOS), or other BLE Clients | for testing reading or writing of characteristic values |
How CBL Works
CBL is a layer of software that translates function calling conventions between IAR and SDCC. It consists of three parts:
- downcall stubs: these are stub functions for SDCC code to call function in IAR-compiled firmware image.
- upcall jump tables: these are target functions whose addresses are known by IAR for callbacks, and they redirect the callback to the appropriate SDCC functions that translate the calling convention from IAR to SDCC.
- reprogramming profile: this is a firmware feature exposed as a BLE service for the user to write the SDCC-compiled firmware into the code flash.
Background: IAR for 8051
IAR for 8051 supports the use of code banking to enable the CC254x MCU to address more than 64KB of code space. The original 8051 ISA supports 16-bit Xdata pointers, which can address 64KB of code space, but the CC254x can have 256 KB of code flash. To address the additional flash space, the MCU supports a code banking mechanism, where each bank is 32KB in size: bank 0 (first 32KB) is always resident, while the other banks (1, 2, …) can be mapped into the upper 32KB of code space by setting special function register (SFR) bits.
IAR compiler supports bank switching and restoration for function calls by means of relay functions. Instead of an LCALL instruction to the target address, the compiler generates a set of relay functions in the root bank to perform bank switching to the target function’s code bank upon calling and restoring the caller’s code bank upon return. This way, the caller just has to call using the function’s corresponding relay function instead, thereby simplifying the code.
We have developed tools to parse the map file generated by IAR and extract the relay function addresses for each target function. Then, our binding layer includes stub functions that then translate the calling convention, call the relay, and translates the return value.
CBL uses a set of inlined assembly macros for translating calling convention within each stub function. The calling conventions are
SDCC Calling Convention
We use --model-small
while tagging all variables with __xdata
in order to keep pointers 2 bytes (to be compatible with IAR). Otherwise, SDCC uses 3-byte pointers, where the top byte can indicate idata or xdata and the bank number. We also use
- the first parameter uses DPH (least significant byte), DPL, B, and ACC as needed. We disallow bit parameters.
- the other parameters are passed on the internal stack by using the
--stack-auto
flag. Note that this means local variables that are declared__xdata
must be static, since SDCC does not use the external stack (and has no concept of IAR’s external stack). The last parameter is pushed first, and each parameter is little endian byte order. - the return value is passed in DPL, DPH, B, and ACC as needed.
- note that the internal stack is hardware supported and grows upwards (from lower address towards upper address)
- SDCC also pushes a frame pointer (named
_bp
) on the stack before the LCALL instruction pushes the return address.
IAR Calling convention
IAR for CC2540 uses the –xdata-reentrant calling convention. This means it makes use of relay functions, passes parameters in registers if possible, and passes the rest of the parameters on the (software-defind) external stack (XSP), which is stored at internal RAM locations 0x18-0x19. It contains the (little-endian byte order) stack that grows downwards (from higher address towards lower address). Parameters are pushed from the last one towards the first, if it cannot find suitable registers to use. It allocates the registers in the following “first-fit” order:
- 1-byte parameters are passed in R1, R2, R3, R4, R5, whichever has not been used.
- 2-byte parameters are passed in R3:R2 (high:low) or R5:R4, if available.
- 3-byte parameters are passed in R3:R2:R1, if possible
- 4-byte parameters are passed in R5:R4:R3:R2, if available
Return values are passed in the following registers:
- 1-byte value: R1
- 2-byte value: R3:R2
- 3-byte value: R3:R2:R1
- 4-byte value: R5:R4:R3:R2
Although IAR supports returning structs, SDCC does not, so we do not have to worry about IAR’s use of a hidden pointer (to space allocated by the caller) for the struct.
Downcall Implementation
For downcalls, we hand-crafted assembly code to copy the parameters passed to the stub functions to match the calling convention of IAR before doing an LCALL to the relay address of the target function, and we copy the return values from IAR convention to SDCC convention upon return, tear down the (external) stack, and return to the SDCC caller. These macros are handcrafted for each sequence of parameter sizes and for each return value size. Additional macros may need to be handcrafted for future functions, but for now all of the function prototypes in the firmware have been covered, and the user does not need to see these macros (defined in the file sdcc2iar.h
in the stub function’s source code).
Upcalls
Upcalls are made from IAR space to SDCC space. IAR compiler also makes use of relay functions when making the upcall, but these functions (to be compiled by SDCC) do not necessarily exist by the time the IAR code is compiled. Our solution is to define a jump table for functions whose addresses are statically allocated, so that IAR can generate the relay functions to these functions in the jump table. Then, SDCC-generated code will fill in those functions in the jump table to jump to the actual call-back functions.
Each SDCC-compiled callback function must include a macro to convert IAR-passed parameters into SDCC ones at the beginning, and it must use a special macro to return value and control back to the IAR caller. These macros are defined in the iar2sdcc.h
header file.
Limitations
The code compiled using SDCC is limited to one code bank (32KB), 2KB of which is devoted to the jump table, so the user code is limited to 30KB. Also, all local variables must be declared static (in order for pointers to work consistently), and total static space is limited to about 270 bytes (as of this writing). However, one can call OSAL’s malloc() to dynamically allocate more memory if needed.
Also, one cannot add new profiles, because the number of callbacks is fixed, as any changes to the number of relay functions will cause the rest of the IAR code to be relocated. However, most applications can simply extend the functionality of existing profiles by handling the reading or writing of additional attributes (just need to handle additional UUIDs or characteristic handles) or types of messages by modifying existing callback functions.