Skip to content

Commit

Permalink
applications: Add USB to UART bridge
Browse files Browse the repository at this point in the history
Adds USB to UART bridge intended for PCA20035.

Signed-off-by: Jan Tore Guggedal <jantore.guggedal@nordicsemi.no>
Signed-off-by: Bernt Johan Damslora <bernt.johan.damslora@nordicsemi.no>
  • Loading branch information
bjda committed Sep 12, 2019
1 parent 5ccdfb2 commit d464df9
Show file tree
Hide file tree
Showing 5 changed files with 343 additions and 0 deletions.
1 change: 1 addition & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# Applications
/applications/asset_tracker/ @joakimtoe @jtguggedal @rlubos
/applications/nrf_desktop/ @pdunaj
/applications/usb_uart_bridge/ @joakimtoe @jtguggedal
# Boards
/boards/ @ioannisg @anangl
# All cmake related files
Expand Down
15 changes: 15 additions & 0 deletions applications/usb_uart_bridge/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#
# Copyright (c) 2018 Nordic Semiconductor
#
# SPDX-License-Identifier: LicenseRef-BSD-5-Clause-Nordic
#

cmake_minimum_required(VERSION 3.8.2)

include(../../cmake/boilerplate.cmake)

include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE)
project(NONE)

target_sources(app PRIVATE src/main.c)
zephyr_include_directories(src)
34 changes: 34 additions & 0 deletions applications/usb_uart_bridge/prj.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#General
CONFIG_REBOOT=y
CONFIG_HEAP_MEM_POOL_SIZE=16384
CONFIG_GPIO=y
CONFIG_POLL=y
CONFIG_BOOTLOADER_MCUBOOT=y

# USB
CONFIG_USB=y
CONFIG_USB_DEVICE_STACK=y
CONFIG_USB_DEVICE_MANUFACTURER="Nordic Semiconductor"
CONFIG_USB_DEVICE_PRODUCT="Thingy:91 UART"
CONFIG_USB_DEVICE_VID=0x1915
CONFIG_USB_DEVICE_PID=0x520F
CONFIG_USB_DEVICE_SN="PCA20035 12PLACEHLDRS"
CONFIG_USB_COMPOSITE_DEVICE=y
CONFIG_USB_CDC_ACM=y
CONFIG_USB_CDC_ACM_RINGBUF_SIZE=4096
CONFIG_USB_CDC_ACM_DEVICE_COUNT=2

# Logging
CONFIG_LOG=y
CONFIG_USE_SEGGER_RTT=y
CONFIG_LOG_BACKEND_RTT=y
CONFIG_LOG_BACKEND_RTT_MODE_DROP=y

# UART
CONFIG_SERIAL=y
CONFIG_UART_INTERRUPT_DRIVEN=y
CONFIG_UART_LINE_CTRL=y
CONFIG_UART_0_NRF_UARTE=y
CONFIG_UART_0_NRF_FLOW_CONTROL=n
CONFIG_UART_1_NRF_UARTE=y
CONFIG_UART_1_NRF_FLOW_CONTROL=n
8 changes: 8 additions & 0 deletions applications/usb_uart_bridge/sample.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
sample:
name: USB-UART bridge
tests:
test_build:
build_only: true
build_on_all: true
platform_whitelist: nrf52840_20035
tags: ci_build
285 changes: 285 additions & 0 deletions applications/usb_uart_bridge/src/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,285 @@
/*
* Copyright (c) 2019 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <zephyr.h>
#include <device.h>
#include <uart.h>
#include <nrfx.h>
#include <string.h>
#include <nrf_power.h>
#include <power/reboot.h>

/* Overriding weak function to set iSerial runtime. */
u8_t *usb_update_sn_string_descriptor(void)
{
static u8_t buf[] = "PCA20035_12PLACEHLDRS";

snprintk(&buf[9], 13, "%04X%08X",
(uint32_t)(NRF_FICR->DEVICEADDR[1] & 0x0000FFFF)|0x0000C000,
(uint32_t)NRF_FICR->DEVICEADDR[0]);

return (u8_t *)&buf;
}

#define POWER_THREAD_STACKSIZE 48
#define POWER_THREAD_PRIORITY K_LOWEST_APPLICATION_THREAD_PRIO

/* Heap block space is always one of 2^(2n) for n from 3 to 7.
* (see reference/kernel/memory/heap.html for more info)
* Here, we target 64-byte blocks. Since we want to fit one struct uart_data
* into each block, the block layout becomes:
* 16 bytes: reserved by the Zephyr heap block descriptor (not in struct)
* 4 bytes: reserved by the Zephyr FIFO (in struct)
* 40 bytes: UART data buffer (in struct)
* 4 bytes: length field (in struct, padded for alignment)
*/
#define UART_BUF_SIZE 40

static K_FIFO_DEFINE(usb_0_tx_fifo);
static K_FIFO_DEFINE(usb_1_tx_fifo);
static K_FIFO_DEFINE(uart_0_tx_fifo);
static K_FIFO_DEFINE(uart_1_tx_fifo);

struct uart_data {
void *fifo_reserved;
u8_t buffer[UART_BUF_SIZE];
u16_t len;
};

static struct serial_dev {
struct device *dev;
void *peer;
struct k_fifo *fifo;
struct k_sem sem;
struct uart_data *rx;
} devs[4];

/* Frees data for incoming transmission on dev_data blocked by full heap. */
int oom_free(struct serial_dev *dev_data)
{
struct serial_dev *peer_dev_data = (struct serial_dev *)dev_data->peer;
struct uart_data *buf;

/* First, try to free from FIFO of peer device (blocked stream) */
buf = k_fifo_get(peer_dev_data->fifo, K_NO_WAIT);
if (buf) {
k_free(buf);
return 0;
}

/* Then, try FIFO of the receiving device (reverse of blocked stream) */
buf = k_fifo_get(dev_data->fifo, K_NO_WAIT);
if (buf) {
k_free(buf);
return 0;
}

/* Finally, try all of them */
for (int i = 0; i < ARRAY_SIZE(devs); i++) {
buf = k_fifo_get(dev_data->fifo, K_NO_WAIT);
if (buf) {
k_free(buf);
return 0;
}
}

return -1; /* Was not able to free any heap memory */
}

static void uart_interrupt_handler(void *user_data)
{
struct serial_dev *dev_data = user_data;
struct device *dev = dev_data->dev;
struct serial_dev *peer_dev_data = (struct serial_dev *)dev_data->peer;
struct uart_data **rx = &dev_data->rx;

uart_irq_update(dev);

while (uart_irq_rx_ready(dev)) {
int data_length;

while (!(*rx)) {
(*rx) = k_malloc(sizeof(*(*rx)));
if ((*rx)) {
(*rx)->len = 0;
} else {
int err = oom_free(dev_data);

if (err) {
printk("Could not free memory. Rebooting.\n");
sys_reboot(SYS_REBOOT_COLD);
}
}
}

data_length = uart_fifo_read(dev, &(*rx)->buffer[(*rx)->len],
UART_BUF_SIZE - (*rx)->len);
(*rx)->len += data_length;

if ((*rx)->len > 0) {
if (((*rx)->len == UART_BUF_SIZE) ||
((*rx)->buffer[(*rx)->len - 1] == '\n') ||
((*rx)->buffer[(*rx)->len - 1] == '\r') ||
((*rx)->buffer[(*rx)->len - 1] == '\0')) {
k_fifo_put(peer_dev_data->fifo, (*rx));
k_sem_give(&peer_dev_data->sem);

(*rx) = NULL;
}
}
}

if (uart_irq_tx_ready(dev)) {
struct uart_data *buf = k_fifo_get(dev_data->fifo, K_NO_WAIT);
u16_t written = 0;

/* Nothing in the FIFO, nothing to send */
if (!buf) {
uart_irq_tx_disable(dev);
return;
}

while (buf->len > written) {
written += uart_fifo_fill(dev,
&buf->buffer[written],
buf->len - written);
}

while (!uart_irq_tx_complete(dev)) {
/* Wait for the last byte to get
* shifted out of the module
*/
}

if (k_fifo_is_empty(dev_data->fifo)) {
uart_irq_tx_disable(dev);
}

k_free(buf);
}
}

void power_thread(void)
{
while (1) {
if (!nrf_power_usbregstatus_vbusdet_get()) {
nrf_power_system_off();
}
k_sleep(100);
}
}

void main(void)
{
int ret;
struct serial_dev *usb_0_dev_data = &devs[0];
struct serial_dev *usb_1_dev_data = &devs[1];
struct serial_dev *uart_0_dev_data = &devs[2];
struct serial_dev *uart_1_dev_data = &devs[3];
struct device *usb_0_dev, *usb_1_dev, *uart_0_dev, *uart_1_dev;

usb_0_dev = device_get_binding("CDC_ACM_0");
if (!usb_0_dev) {
printk("CDC ACM device not found\n");
return;
}

usb_1_dev = device_get_binding("CDC_ACM_1");
if (!usb_1_dev) {
printk("CDC ACM device not found\n");
return;
}

uart_0_dev = device_get_binding("UART_0");
if (!uart_0_dev) {
printk("UART 0 init failed\n");
}

uart_1_dev = device_get_binding("UART_1");
if (!uart_1_dev) {
printk("UART 1 init failed\n");
}

usb_0_dev_data->dev = usb_0_dev;
usb_0_dev_data->fifo = &usb_0_tx_fifo;
usb_0_dev_data->peer = uart_0_dev_data;

usb_1_dev_data->dev = usb_1_dev;
usb_1_dev_data->fifo = &usb_1_tx_fifo;
usb_1_dev_data->peer = uart_1_dev_data;

uart_0_dev_data->dev = uart_0_dev;
uart_0_dev_data->fifo = &uart_0_tx_fifo;
uart_0_dev_data->peer = usb_0_dev_data;

uart_1_dev_data->dev = uart_1_dev;
uart_1_dev_data->fifo = &uart_1_tx_fifo;
uart_1_dev_data->peer = usb_1_dev_data;

k_sem_init(&usb_0_dev_data->sem, 0, 1);
k_sem_init(&usb_1_dev_data->sem, 0, 1);
k_sem_init(&uart_0_dev_data->sem, 0, 1);
k_sem_init(&uart_1_dev_data->sem, 0, 1);

uart_irq_callback_user_data_set(usb_0_dev, uart_interrupt_handler,
usb_0_dev_data);
uart_irq_callback_user_data_set(usb_1_dev, uart_interrupt_handler,
usb_1_dev_data);
uart_irq_callback_user_data_set(uart_0_dev, uart_interrupt_handler,
uart_0_dev_data);
uart_irq_callback_user_data_set(uart_1_dev, uart_interrupt_handler,
uart_1_dev_data);

uart_irq_rx_enable(usb_0_dev);
uart_irq_rx_enable(usb_1_dev);
uart_irq_rx_enable(uart_0_dev);
uart_irq_rx_enable(uart_1_dev);

printk("USB <--> UART bridge is now initialized\n");

struct k_poll_event events[4] = {
K_POLL_EVENT_STATIC_INITIALIZER(K_POLL_TYPE_SEM_AVAILABLE,
K_POLL_MODE_NOTIFY_ONLY,
&usb_0_dev_data->sem, 0),
K_POLL_EVENT_STATIC_INITIALIZER(K_POLL_TYPE_SEM_AVAILABLE,
K_POLL_MODE_NOTIFY_ONLY,
&usb_1_dev_data->sem, 0),
K_POLL_EVENT_STATIC_INITIALIZER(K_POLL_TYPE_SEM_AVAILABLE,
K_POLL_MODE_NOTIFY_ONLY,
&uart_0_dev_data->sem, 0),
K_POLL_EVENT_STATIC_INITIALIZER(K_POLL_TYPE_SEM_AVAILABLE,
K_POLL_MODE_NOTIFY_ONLY,
&uart_1_dev_data->sem, 0),
};

while (1) {
ret = k_poll(events, ARRAY_SIZE(events), K_FOREVER);
if (ret != 0) {
continue;
}

if (events[0].state == K_POLL_TYPE_SEM_AVAILABLE) {
events[0].state = K_POLL_STATE_NOT_READY;
k_sem_take(&usb_0_dev_data->sem, K_NO_WAIT);
uart_irq_tx_enable(usb_0_dev);
} else if (events[1].state == K_POLL_TYPE_SEM_AVAILABLE) {
events[1].state = K_POLL_STATE_NOT_READY;
k_sem_take(&usb_1_dev_data->sem, K_NO_WAIT);
uart_irq_tx_enable(usb_1_dev);
} else if (events[2].state == K_POLL_TYPE_SEM_AVAILABLE) {
events[2].state = K_POLL_STATE_NOT_READY;
k_sem_take(&uart_0_dev_data->sem, K_NO_WAIT);
uart_irq_tx_enable(uart_0_dev);
} else if (events[3].state == K_POLL_TYPE_SEM_AVAILABLE) {
events[3].state = K_POLL_STATE_NOT_READY;
k_sem_take(&uart_1_dev_data->sem, K_NO_WAIT);
uart_irq_tx_enable(uart_1_dev);
}
}
}

K_THREAD_DEFINE(power_thread_id, POWER_THREAD_STACKSIZE, power_thread,
NULL, NULL, NULL, POWER_THREAD_PRIORITY, 0, K_NO_WAIT);

0 comments on commit d464df9

Please sign in to comment.