This repo contains a small footprint AT command client for talking to the following u-blox u-connectXpress short-range modules:
- NORA-W36
- NORA-B26
The client can run on both bare-metal and OS systems using a tiny porting layer (see Porting and Configuration)
There are two levels of APIs included in this repo; the lower uAtClient API and the upper u-connectXpress API.
See the examples/ directory for small, working examples that run on Windows, Linux and STM32F4. If you want a Windows desktop example that integrates the
ucxclient, check out the ucx-windows-app
project which demonstrates using the ucxclient on Windows with a TUI front-end.
Please note: The code in this repo is in experimental status and changes to the APIs are to be expected.
This API contains an AT client implementation that handles transmission of AT commands, reception and parsing of AT responses and URCs. You will find the uAtClient API in the inc/ directory.
#include "u_cx_at_client.h"
#include "u_port.h"
void main(void)
{
int32_t status;
static char rxBuf[1024];
static char urcBuf[1024];
// Configure AT client with buffers and UART device name
static uCxAtClientConfig_t config = {
.pRxBuffer = &rxBuf[0],
.rxBufferLen = sizeof(rxBuf),
.pUrcBuffer = &urcBuf[0],
.urcBufferLen = sizeof(urcBuf),
.pUartDevName = "UART0"
};
// Initialize port layer
uPortInit();
uCxAtClient_t client;
// Initialize AT client (this also starts background RX task if port implements it)
uCxAtClientInit(&config, &client);
// Open UART connection
uCxAtClientOpen(&client, 115200, true);
// Example of executing AT command without any AT response
// To execute command without any response use the uCxAtClientExecSimpleCmd(F)
// uCxAtClientExecSimpleCmdF() uses a format string for the AT command params where
// each character represent a parameter type (a bit like printf)
status = uCxAtClientExecSimpleCmdF(&client, "AT+USYUS=", "dd",
115200, 0, U_CX_AT_UTIL_PARAM_LAST);
printf("'AT+USYUS=' status: %d\n", status);
// Example of executing AT command with an AT response
// Execute cmd 'AT+USYUS?
uCxAtClientCmdBeginF(&client, "AT+USYUS?", "", U_CX_AT_UTIL_PARAM_LAST);
// Read out the AT response
int32_t baudrate;
int32_t flowControl;
status = uCxAtClientCmdGetRspParamsF(&client, "+USYUS:", NULL, NULL, "dd",
&baudrate, &flowControl, U_CX_AT_UTIL_PARAM_LAST);
printf("Response params: %d\n", status);
// All AT client APIs that ends with 'Begin' (such as uCxAtClientCmdBeginF())
// must be terminated by calling uCxAtClientCmdEnd().
// This where you'll get the AT command status
status = uCxAtClientCmdEnd(&client);
printf("'AT+USYUS?' status: %d, baudrate: %d, flowControl: %d\n", status, baudrate, flowControl);
}This API is a higher level API that that simplifies communication with new u-connectXpress u-blox modules (see support list at the top of this page). Using this API eliminates the need of manually sending AT commands to the module. You will find the u-connectXpress API in the ucx_api/ directory.
#include "u_cx_at_client.h"
#include "u_cx.h"
#include "u_cx_system.h"
void main(void)
{
int32_t status;
uCxAtClient_t client;
uCxHandle_t ucxHandle;
// Initialize port layer
uPortInit();
// Configure and initialize AT client (see uAtClient API example for details)
uCxAtClientInit(&config, &client);
// Open UART connection
uCxAtClientOpen(&client, 115200, true);
// Initialize upper u-connectXpress layer
uCxInit(&client, &ucxHandle);
// This will send the "AT+USYUS=" AT command
status = uCxSystemSetUartSettings2(&ucxHandle, 115200, 0);
printf("uCxSystemSetUartSettings2(): %d\n", status);
// This will send the "AT+USYUS?" AT command and parse the AT response params to &settings
uCxSystemGetUartSettings_t settings;
status = uCxSystemGetUartSettings(&ucxHandle, &settings);
printf("uCxSystemGetUartSettings(): %d, baudrate: %d, flow control: %d\n",
status, settings.baud_rate, settings.flow_control);
}The project uses PyInvoke for build automation. Install it with:
pip install invoke# From project root - see all available tasks
invoke --list
# Build examples (from project root, prefix with 'examples.')
invoke examples.linux.http # Build HTTP example for Linux
invoke examples.stm32.http --docker # Build HTTP example for STM32 using Docker
# Or from examples/ directory (without 'examples.' prefix)
cd examples
invoke --list # See all available example tasks
invoke linux.http # Build HTTP example for LinuxSee examples/README.md for complete build instructions and how to run the examples.
# Run Ceedling unit tests
invoke test.ceedling.run
# Run Zephyr Twister tests (automatically sets up west workspace)
invoke test.zephyr.run
# Clean test artifacts
invoke test.ceedling.clean
invoke test.zephyr.clean
invoke test.zephyr.clean-west # Remove west workspaceThe easiest way to try ucxclient is using GitHub Codespaces. Click the button below to open a fully configured development environment in your browser:
The Codespace includes:
- Pre-installed ARM toolchain and Renode emulator
- Example that runs on an emulated STM32F4 board connected to a mocked u-connectXpress module
- Ready-to-use VS Code debug configuration
Once the Codespace is ready, you will see a welcome text with further instructions.
The porting layer is defined in ports/u_port.h and the AT client configuration is in inc/u_cx_at_config.h.
When compiling you can specify:
U_CX_PORT_HEADER_FILE- for port-specific defines (mutexes, time, UART, etc.) included by ports/u_port.hU_CX_AT_CONFIG_FILE- for AT client configuration overrides (timeouts, logging, error codes, etc.) included by inc/u_cx_at_config.h
For example with GCC: -DU_CX_PORT_HEADER_FILE=\"my_u_port.h\" and/or -DU_CX_AT_CONFIG_FILE=\"my_u_cx_config.h\".
Some things are not required for successfully running the AT client (such as U_CX_PORT_PRINTF for logging, U_CX_AT_PORT_ASSERT), but the following are required:
| Function | Description |
|---|---|
| U_CX_PORT_GET_TIME_MS | Must return a 32 bit timestamp in milliseconds. |
| uPortUartOpen() | Opens a UART device and returns a platform-specific handle. |
| uPortUartRead() | Reads data from UART with a timeout in millisec. Must return the number of bytes received, 0 if there are no data available within the timeout or negative value on error. |
| uPortUartWrite() | Writes data to the UART. Must return the number of actual bytes written or negative number on error. |
| uPortUartClose() | Closes the UART device. |
| uPortBgRxTaskCreate() | Optional: Creates a background task/thread to automatically call uCxAtClientHandleRx(). If not implemented, user must call uCxAtClientHandleRx() manually (see no-OS port). |
Note: With the new API, you typically don't call uPortUartOpen() directly - instead use uCxAtClientOpen() which handles UART initialization internally.
For systems running RTOS you will also need to port the mutex API below - for bare-metal systems you can use ports/os/u_port_no_os.h:
| Define | Example (Posix) | Description |
|---|---|---|
| U_CX_MUTEX_HANDLE | pthread_mutex_t |
Define this to the mutex type of your system. |
| U_CX_MUTEX_CREATE(mutex) | pthread_mutex_init(&mutex, NULL) |
If your system need to call a function before the mutex can be used, then define it here. Note for examples: The provided examples use mutexes for event signaling between threads/tasks, which requires the mutex to support being unlocked by a different thread/task than the one that locked it. For such use cases, consider using binary semaphores instead of recursive mutexes, and ensure they are initialized to an available/unlocked state. |
| U_CX_MUTEX_DELETE(mutex) | pthread_mutex_destroy(&mutex) |
If your system has a function to de-allocate a mutex, then define it here. |
| U_CX_MUTEX_LOCK(mutex) | pthread_mutex_lock(&mutex) |
Define this to corresponding "lock"/"take" function of your system. No return value is expected (any return value will be ignored). |
| U_CX_MUTEX_TRY_LOCK(mutex, timeoutMs) | uPortMutexTryLock(&mutex, timeoutMs)1 |
Define this to a function that tries to lock/take the mutex but with a timeout timeoutMs in millisec. Must return 0 if the mutex is successfully taken/locked and can return any negative value on timeout. |
| U_CX_MUTEX_UNLOCK(mutex) | pthread_mutex_unlock(&mutex) |
Define this to corresponding "unlock"/"give" function of your system. No return value is expected (any return value will be ignored). |
1 See ports/os/u_port_posix.c
You will find example ports in ports/. These ports are used by the example code and you will find more information in ports/README.md.
The port layer is split into:
- OS abstraction (ports/os/): Mutex, time, and optional background RX task
- UART abstraction (ports/uart/): Platform-specific UART I/O
Available ports include:
- POSIX (Linux/macOS): pthreads-based with termios UART
- Windows: Windows API with COM port support
- Zephyr RTOS: Integrated as a Zephyr module
- FreeRTOS + STM32F4: ARM Cortex-M4 port (see ports/extra/stm32f4/README.md)
- No-OS: Bare-metal systems (user-driven RX polling)
Copyright © u-blox
u-blox reserves all rights in this deliverable (documentation, software, etc., hereafter “Deliverable”).
u-blox grants you the right to use, copy, modify and distribute the Deliverable provided hereunder for any purpose without fee.
THIS DELIVERABLE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED WARRANTY. IN PARTICULAR, NEITHER THE AUTHOR NOR U-BLOX MAKES ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY OF THIS DELIVERABLE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
In case you provide us a feedback or make a contribution in the form of a further development of the Deliverable (“Contribution”), u-blox will have the same rights as granted to you, namely to use, copy, modify and distribute the Contribution provided to us for any purpose without fee.