Add initial CAN FD CDC composite firmware

- Custom board definition for STM32G0B1KBU6 (ews_board)
- Zephyr-based firmware with modular architecture
- USB composite device: gs_usb CAN FD + CDC ACM interfaces
- PFET control via CDC text commands (PA8, PB2)
- Status LED on PB4, CAN FD on PB0/PB1
- No external crystal - uses HSI with USB clock recovery
- Ready for build testing with: west build -b ews_board
This commit is contained in:
Eduard Iten 2025-12-08 11:27:15 +01:00
parent 69b8cb0b79
commit 3d328fb7a2
14 changed files with 652 additions and 0 deletions

View File

@ -0,0 +1,11 @@
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(ews_canfd_cdc_composite)
target_sources(app PRIVATE
src/main.c
src/cdc_handler.c
src/pfet_control.c
src/gs_usb_can.c
)

View File

@ -0,0 +1,47 @@
# CAN FD CDC Composite Firmware
A Zephyr-based firmware for the EWS board that provides:
- CAN FD to gs_usb interface (similar to candlelight)
- USB CDC interface for PFET control
- USB composite device functionality
## Features
- **CAN FD Support**: Full CAN FD protocol support via gs_usb interface
- **USB Composite**: Single USB device with multiple interfaces:
- gs_usb interface for CAN communication
- CDC ACM interface for PFET control and status
- **PFET Control**: Control both output PFETs via CDC commands
- **Status LEDs**: Control status LEDs for visual feedback
- **Compatible**: Works with standard CAN utilities (can-utils, etc.)
## Hardware Target
- **MCU**: STM32G0B1KBU6 (on EWS board)
- **CAN**: CAN FD via FDCAN1 (PB0/PB1) with SN65HVD230 transceiver
- **USB**: USB 2.0 Full Speed (PA11/PA12)
- **GPIOs**:
- PFET1 control: PA8
- PFET2 control: PB2
- Status LED: PB4
## Build Requirements
- Zephyr RTOS (v3.5+)
- West build tool
- ARM GCC toolchain
## Building
```bash
west build -b ews_board
```
## CDC Protocol
The CDC interface uses simple text commands:
- `PFET1_ON\n` - Turn on PFET1
- `PFET1_OFF\n` - Turn off PFET1
- `PFET2_ON\n` - Turn on PFET2
- `PFET2_OFF\n` - Turn off PFET2
- `STATUS\n` - Get current PFET status

View File

@ -0,0 +1,10 @@
identifier: ews_board
name: EWS Board
type: mcu
arch: arm
family: stm32
series: stm32g0x
socs:
- name: stm32g0b1xx
testing:
default: true

View File

@ -0,0 +1,47 @@
# EWS Board
## Overview
The EWS Board is based on the STM32G0B1KBU6 microcontroller in UFQFPN32 package.
## Hardware Features
- STM32G0B1KBU6 MCU (Arm Cortex-M0+ core, 128 KB Flash, 36 KB RAM)
- Internal HSI oscillator with USB clock recovery
- USB 2.0 Full Speed interface
- CAN FD interface
- Status LED on PB4
- Two PFET control outputs (PA8, PB2)
## Pin Configuration
| Function | Pin | Notes |
|----------|-----|-------|
| Status LED | PB4 | Active high |
| PFET1 Control | PA8 | Active high |
| PFET2 Control | PB2 | Active high |
| CAN RX | PB0 | FDCAN1_RX |
| CAN TX | PB1 | FDCAN1_TX |
| USB D- | PA11 | USB_DM |
| USB D+ | PA12 | USB_DP |
## Clock Configuration
The board uses the internal HSI oscillator (16 MHz) with PLL to generate:
- System clock: 64 MHz
- USB clock: 48 MHz (from PLL Q output)
- CAN clock: 64 MHz
No external crystal is used; USB clock recovery ensures accurate timing for USB communication.
## Programming and Debugging
The board supports programming via:
- USB DFU (built-in STM32 bootloader)
- SWD interface (if exposed)
## Building Firmware
```bash
west build -b ews_board
```

View File

@ -0,0 +1,104 @@
/dts-v1/;
#include <st/g0/stm32g0b1Xb.dtsi>
#include <st/g0/stm32g0b1k(b-c-e)ux-pinctrl.dtsi>
/ {
model = "EWS Board STM32G0B1KBU6";
compatible = "ews,ews-board";
chosen {
zephyr,console = &cdc_acm_uart0;
zephyr,shell-uart = &cdc_acm_uart0;
zephyr,sram = &sram0;
zephyr,flash = &flash0;
zephyr,canbus = &fdcan1;
};
leds {
compatible = "gpio-leds";
status_led: led_0 {
gpios = <&gpiob 4 GPIO_ACTIVE_HIGH>;
label = "Status LED";
};
};
pfets {
compatible = "gpio-leds";
pfet1: pfet_1 {
gpios = <&gpioa 8 GPIO_ACTIVE_HIGH>;
label = "PFET1 Control";
};
pfet2: pfet_2 {
gpios = <&gpiob 2 GPIO_ACTIVE_HIGH>;
label = "PFET2 Control";
};
};
aliases {
led0 = &status_led;
pfet0 = &pfet1;
pfet1 = &pfet2;
};
};
&clk_hsi {
status = "okay";
};
&pll {
div-m = <1>;
mul-n = <8>;
div-q = <2>;
div-r = <2>;
clocks = <&clk_hsi>;
status = "okay";
};
&rcc {
clocks = <&pll>;
clock-frequency = <DT_FREQ_M(64)>;
ahb-prescaler = <1>;
apb1-prescaler = <1>;
};
&fdcan1 {
pinctrl-0 = <&fdcan1_rx_pb0 &fdcan1_tx_pb1>;
pinctrl-names = "default";
bus-speed = <500000>;
bus-speed-data = <2000000>;
status = "okay";
};
&usb {
pinctrl-0 = <&usb_dm_pa11 &usb_dp_pa12>;
pinctrl-names = "default";
status = "okay";
cdc_acm_uart0: cdc_acm_uart0 {
compatible = "zephyr,cdc-acm-uart";
};
};
&gpioa {
status = "okay";
};
&gpiob {
status = "okay";
};
&flash0 {
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
boot_partition: partition@0 {
label = "mcuboot";
reg = <0x00000000 0x00002000>;
};
slot0_partition: partition@2000 {
label = "image-0";
reg = <0x00002000 0x0001E000>;
};
};
};

View File

@ -0,0 +1,22 @@
# EWS Board Configuration
CONFIG_SOC_SERIES_STM32G0X=y
CONFIG_SOC_STM32G0B1XX=y
# Clock configuration - USB clock sync, no external crystal
CONFIG_CLOCK_CONTROL=y
CONFIG_CLOCK_STM32_HSI=y
CONFIG_CLOCK_STM32_PLL_SRC_HSI=y
CONFIG_CLOCK_STM32_PLL_M_DIVISOR=1
CONFIG_CLOCK_STM32_PLL_N_MULTIPLIER=8
CONFIG_CLOCK_STM32_PLL_Q_DIVISOR=2
CONFIG_CLOCK_STM32_PLL_R_DIVISOR=2
# USB 48MHz from PLL
CONFIG_CLOCK_STM32_PLL_Q_DIVISOR=2
# Enable GPIO
CONFIG_GPIO=y
# Enable CAN
CONFIG_CAN=y

View File

@ -0,0 +1,28 @@
CONFIG_USB_DEVICE_STACK=y
CONFIG_USB_DEVICE_COMPOSITE=y
# USB CDC ACM
CONFIG_USB_CDC_ACM=y
CONFIG_SERIAL=y
CONFIG_UART_CONSOLE=n
CONFIG_USB_UART_CONSOLE=y
# CAN configuration
CONFIG_CAN=y
CONFIG_CAN_FD_MODE=y
# Networking for gs_usb
CONFIG_NETWORKING=y
CONFIG_NET_SOCKETS=y
CONFIG_NET_SOCKETS_CAN=y
# GPIO for PFET control
CONFIG_GPIO=y
# Logging
CONFIG_LOG=y
CONFIG_USB_DEVICE_LOG_LEVEL_DBG=y
# System
CONFIG_MAIN_STACK_SIZE=2048
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048

View File

@ -0,0 +1,87 @@
/*
* CDC Handler Module
* Handles USB CDC ACM commands for PFET control
*/
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/usb/usb_device.h>
#include <zephyr/logging/log.h>
#include <string.h>
#include "cdc_handler.h"
#include "pfet_control.h"
LOG_MODULE_REGISTER(cdc_handler, LOG_LEVEL_DBG);
#define CDC_DEVICE_NAME "CDC_ACM_0"
#define RX_BUF_SIZE 64
static const struct device *cdc_dev;
static char rx_buf[RX_BUF_SIZE];
static int rx_pos = 0;
static void process_command(char *cmd)
{
char response[128];
if (strncmp(cmd, "PFET1_ON", 8) == 0) {
pfet_set_state(1, true);
strcpy(response, "PFET1 ON\r\n");
} else if (strncmp(cmd, "PFET1_OFF", 9) == 0) {
pfet_set_state(1, false);
strcpy(response, "PFET1 OFF\r\n");
} else if (strncmp(cmd, "PFET2_ON", 8) == 0) {
pfet_set_state(2, true);
strcpy(response, "PFET2 ON\r\n");
} else if (strncmp(cmd, "PFET2_OFF", 9) == 0) {
pfet_set_state(2, false);
strcpy(response, "PFET2 OFF\r\n");
} else if (strncmp(cmd, "STATUS", 6) == 0) {
snprintf(response, sizeof(response),
"PFET1: %s, PFET2: %s\r\n",
pfet_get_state(1) ? "ON" : "OFF",
pfet_get_state(2) ? "ON" : "OFF");
} else {
strcpy(response, "ERROR: Unknown command\r\n");
}
/* Send response */
uart_poll_out_string(cdc_dev, response);
}
int cdc_handler_init(void)
{
cdc_dev = device_get_binding(CDC_DEVICE_NAME);
if (!cdc_dev) {
LOG_ERR("CDC ACM device not found");
return -ENODEV;
}
LOG_INF("CDC handler initialized");
return 0;
}
void cdc_handler_process(void)
{
char c;
/* Check for incoming characters */
while (uart_poll_in(cdc_dev, (unsigned char *)&c) == 0) {
if (c == '\n' || c == '\r') {
/* End of command */
if (rx_pos > 0) {
rx_buf[rx_pos] = '\0';
process_command(rx_buf);
rx_pos = 0;
}
} else if (rx_pos < (RX_BUF_SIZE - 1)) {
/* Add character to buffer */
rx_buf[rx_pos++] = c;
} else {
/* Buffer full, reset */
rx_pos = 0;
}
}
}

View File

@ -0,0 +1,20 @@
/*
* CDC Handler Header
*/
#ifndef CDC_HANDLER_H
#define CDC_HANDLER_H
/**
* Initialize CDC handler
* @return 0 on success, negative error code on failure
*/
int cdc_handler_init(void);
/**
* Process incoming CDC commands
* Should be called regularly from main loop
*/
void cdc_handler_process(void);
#endif /* CDC_HANDLER_H */

View File

@ -0,0 +1,62 @@
/*
* gs_usb CAN Interface
* Implements gs_usb protocol for CAN FD communication
*/
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/can.h>
#include <zephyr/logging/log.h>
#include "gs_usb_can.h"
LOG_MODULE_REGISTER(gs_usb_can, LOG_LEVEL_DBG);
/* CAN device */
static const struct device *can_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_canbus));
int gs_usb_can_init(void)
{
int ret;
if (!device_is_ready(can_dev)) {
LOG_ERR("CAN device not ready");
return -ENODEV;
}
/* Configure CAN timing for 500kbps (adjust as needed) */
struct can_timing timing = {
.sjw = 1,
.prop_seg = 6,
.phase_seg1 = 7,
.phase_seg2 = 2,
.prescaler = 6
};
ret = can_set_timing(can_dev, &timing);
if (ret < 0) {
LOG_ERR("Failed to set CAN timing: %d", ret);
return ret;
}
/* Start CAN controller */
ret = can_start(can_dev);
if (ret < 0) {
LOG_ERR("Failed to start CAN: %d", ret);
return ret;
}
LOG_INF("gs_usb CAN interface initialized");
return 0;
}
int gs_usb_can_send_frame(const struct can_frame *frame)
{
return can_send(can_dev, frame, K_FOREVER, NULL, NULL);
}
/* TODO: Implement full gs_usb protocol */
/* This would require implementing the USB bulk endpoints and
gs_usb command/response protocol as defined in:
https://github.com/candle-usb/candleLight_fw
*/

View File

@ -0,0 +1,23 @@
/*
* gs_usb CAN Interface Header
*/
#ifndef GS_USB_CAN_H
#define GS_USB_CAN_H
#include <zephyr/drivers/can.h>
/**
* Initialize gs_usb CAN interface
* @return 0 on success, negative error code on failure
*/
int gs_usb_can_init(void);
/**
* Send a CAN frame
* @param frame CAN frame to send
* @return 0 on success, negative error code on failure
*/
int gs_usb_can_send_frame(const struct can_frame *frame);
#endif /* GS_USB_CAN_H */

View File

@ -0,0 +1,82 @@
/*
* EWS CAN FD CDC Composite Firmware
* Main application entry point
*/
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/usb/usb_device.h>
#include <zephyr/logging/log.h>
#include "cdc_handler.h"
#include "pfet_control.h"
#include "gs_usb_can.h"
LOG_MODULE_REGISTER(main, LOG_LEVEL_DBG);
/* LED definitions - adjust to actual EWS board pins */
#define LED_STATUS_NODE DT_ALIAS(led0)
static const struct gpio_dt_spec led_status = GPIO_DT_SPEC_GET(LED_STATUS_NODE, gpios);
int main(void)
{
int ret;
LOG_INF("EWS CAN FD CDC Composite Firmware starting...");
/* Initialize status LED */
if (!gpio_is_ready_dt(&led_status)) {
LOG_ERR("Status LED device not ready");
return -ENODEV;
}
ret = gpio_pin_configure_dt(&led_status, GPIO_OUTPUT_ACTIVE);
if (ret < 0) {
LOG_ERR("Error configuring status LED: %d", ret);
return ret;
}
/* Initialize PFET control */
ret = pfet_control_init();
if (ret < 0) {
LOG_ERR("Failed to initialize PFET control: %d", ret);
return ret;
}
/* Initialize USB composite device */
ret = usb_enable(NULL);
if (ret != 0) {
LOG_ERR("Failed to enable USB: %d", ret);
return ret;
}
/* Initialize CDC handler */
ret = cdc_handler_init();
if (ret < 0) {
LOG_ERR("Failed to initialize CDC handler: %d", ret);
return ret;
}
/* Initialize gs_usb CAN interface */
ret = gs_usb_can_init();
if (ret < 0) {
LOG_ERR("Failed to initialize gs_usb CAN: %d", ret);
return ret;
}
LOG_INF("EWS firmware initialized successfully");
/* Main loop */
while (1) {
/* Toggle status LED to show activity */
gpio_pin_toggle_dt(&led_status);
/* Handle CDC commands */
cdc_handler_process();
k_msleep(500);
}
return 0;
}

View File

@ -0,0 +1,78 @@
/*
* PFET Control Module
* Controls the two output PFETs on the EWS board
*/
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/logging/log.h>
#include "pfet_control.h"
LOG_MODULE_REGISTER(pfet_control, LOG_LEVEL_DBG);
/* PFET control pin definitions */
static const struct gpio_dt_spec pfet1 = GPIO_DT_SPEC_GET(DT_ALIAS(pfet0), gpios);
static const struct gpio_dt_spec pfet2 = GPIO_DT_SPEC_GET(DT_ALIAS(pfet1), gpios);
static bool pfet1_state = false;
static bool pfet2_state = false;
int pfet_control_init(void)
{
int ret;
if (!gpio_is_ready_dt(&pfet1) || !gpio_is_ready_dt(&pfet2)) {
LOG_ERR("PFET GPIO devices not ready");
return -ENODEV;
}
/* Configure PFET pins as output, initially off */
ret = gpio_pin_configure_dt(&pfet1, GPIO_OUTPUT_INACTIVE);
if (ret < 0) {
LOG_ERR("Failed to configure PFET1 pin: %d", ret);
return ret;
}
ret = gpio_pin_configure_dt(&pfet2, GPIO_OUTPUT_INACTIVE);
if (ret < 0) {
LOG_ERR("Failed to configure PFET2 pin: %d", ret);
return ret;
}
LOG_INF("PFET control initialized");
return 0;
}
int pfet_set_state(int pfet_num, bool state)
{
int ret;
if (pfet_num == 1) {
ret = gpio_pin_set_dt(&pfet1, state ? 1 : 0);
if (ret == 0) {
pfet1_state = state;
LOG_INF("PFET1 %s", state ? "ON" : "OFF");
}
} else if (pfet_num == 2) {
ret = gpio_pin_set_dt(&pfet2, state ? 1 : 0);
if (ret == 0) {
pfet2_state = state;
LOG_INF("PFET2 %s", state ? "ON" : "OFF");
}
} else {
return -EINVAL;
}
return ret;
}
bool pfet_get_state(int pfet_num)
{
if (pfet_num == 1) {
return pfet1_state;
} else if (pfet_num == 2) {
return pfet2_state;
}
return false;
}

View File

@ -0,0 +1,31 @@
/*
* PFET Control Header
*/
#ifndef PFET_CONTROL_H
#define PFET_CONTROL_H
#include <stdbool.h>
/**
* Initialize PFET control
* @return 0 on success, negative error code on failure
*/
int pfet_control_init(void);
/**
* Set PFET state
* @param pfet_num PFET number (1 or 2)
* @param state true for ON, false for OFF
* @return 0 on success, negative error code on failure
*/
int pfet_set_state(int pfet_num, bool state);
/**
* Get current PFET state
* @param pfet_num PFET number (1 or 2)
* @return current state (true=ON, false=OFF)
*/
bool pfet_get_state(int pfet_num);
#endif /* PFET_CONTROL_H */