Skip to content

Commit

Permalink
serial 1wire passthrough for ESCs with the BlHeli bootloader
Browse files Browse the repository at this point in the history
  • Loading branch information
nathantsoi committed Jul 22, 2015
1 parent 7af3d57 commit d374391
Show file tree
Hide file tree
Showing 9 changed files with 445 additions and 0 deletions.
1 change: 1 addition & 0 deletions Makefile
Expand Up @@ -238,6 +238,7 @@ COMMON_SRC = build_config.c \
io/rc_controls.c \
io/rc_curves.c \
io/serial.c \
io/serial_1wire.c \
io/serial_cli.c \
io/serial_msp.c \
io/statusindicator.c \
Expand Down
95 changes: 95 additions & 0 deletions docs/1wire.md
@@ -0,0 +1,95 @@
# 1-wire passthrough esc programming

Currently supported on the STM32F3DISCOVERY, NAZE32 (including clones such as the FLIP32) and CC3D.

## Wiring

- For the NAZE, no external wiring is necessary. Simply plugin the board via USB cable.

- For the CC3D, connect [a USB to UART adapter](http://bit.ly/cf-cp2102) to the main port. If you need one, I prefer the [CP2102](http://bit.ly/cf-cp2102) as it is cheap and [the driver](https://www.silabs.com/products/mcu/Pages/USBtoUARTBridgeVCPDrivers.aspx) is readily available.

- In the case that your board does not power on fully without a battery attached, it is OK to attach the battery before following the steps below. However, it may not be necessary in all cases.

## Usage

- Plug in the USB cable and connect to your board with the CleanFlight configurator.

- Open the CLI tab, then run: `1wire <esc index>`

E.g. to connect to the ESC on your flight controller's port #1, run the command:

```
1wire 1
```

- Click "Disconnect" in the CleanFlight configurator. Do not power down your board.

- Note, in the future it may be possible to configure your ESCs directly in CleanFlight.

- Open the BlHeli Suite.

- Ensure you have selected the correct Atmel or SILABS "(USB/Com)" option under the "Select ATMEL / SILABS Interface" menu option.

- Ensure you have the correct port selected.

- On the NAZE, this port will be the same COM port used by the CleanFlight configurator.

- On the CC3D, this port will be your USB to UART serial adapter.

- Click "Connect" and wait for the connection to complete. If you get a COM error, hit connect again. It will probably work.

- Click "Read Setup"

- Use BlHeli suite as normal.

- When you're finished with one ESC, click "Disconnect" then power down the board. E.g. remove the flight battery and unplug the USB cable. Then repeat the whole process for the next ESC

- In the future, powering down the board can be avoided and all ESCs can be configured by CleanFlight. I'll be working on this next...

## Implementing and Configuring targets

The following parameters can be used to enable and configure this in the related target.h file:

USE_SERIAL_1WIRE Enables the 1wire code, defined in target.h


- For new targets

- in `target.h`

```
// Turn on serial 1wire passthrough
#define USE_SERIAL_1WIRE
// How many escs does this board support?
#define ESC_COUNT 6
// STM32F3DISCOVERY TX - PC3 connects to UART RX
#define S1W_TX_GPIO GPIOC
#define S1W_TX_PIN GPIO_Pin_3
// STM32F3DISCOVERY RX - PC1 connects to UART TX
#define S1W_RX_GPIO GPIOC
#define S1W_RX_PIN GPIO_Pin_1
```

- in `serial_1wire.c`

```
// Define your esc hardware
#if defined(STM32F3DISCOVERY) && !(defined(CHEBUZZF3))
const escHardware_t escHardware[ESC_COUNT] = {
{ GPIOD, 12 },
{ GPIOD, 13 },
{ GPIOD, 14 },
{ GPIOD, 15 },
{ GPIOA, 1 },
{ GPIOA, 2 }
};
```

## Development Notes

On the STM32F3DISCOVERY, an external pullup on the ESC line may be necessary. I needed a 3v, 4.7k pullup.

## Todo

Implement the BlHeli bootloader configuration protocol in the CleanFlight GUI

1 change: 1 addition & 0 deletions docs/Cli.md
Expand Up @@ -73,6 +73,7 @@ Re-apply any new defaults as desired.

| `Command` | Description |
|------------------|------------------------------------------------|
| `1wire <esc>` | passthrough 1wire to the specified esc |
| `adjrange` | show/set adjustment ranges settings |
| `aux` | show/set aux settings |
| `mmix` | design custom motor mixer |
Expand Down
233 changes: 233 additions & 0 deletions src/main/io/serial_1wire.c
@@ -0,0 +1,233 @@
/*
* This file is part of Cleanflight.
*
* Cleanflight is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Cleanflight is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Cleanflight. If not, see <http://www.gnu.org/licenses/>.
*
* Ported from https://github.com/4712/BLHeliSuite/blob/master/Interfaces/Arduino1Wire/Source/Arduino1Wire_C/Arduino1Wire.c
* by Nathan Tsoi <nathan@vertile.com>
*/

#include <stdbool.h>

#include "platform.h"

#ifdef USE_SERIAL_1WIRE

#include "drivers/gpio.h"
#include "drivers/inverter.h"
#include "drivers/light_led.h"
#include "drivers/system.h"
#include "io/serial_1wire.h"

// Figure out esc clocks and pins, extrapolated from timer.c
// Periphs could be pulled progmatically... but I'll leave that for another exercise
#if defined(STM32F3DISCOVERY) && !(defined(CHEBUZZF3))
const escHardware_t escHardware[ESC_COUNT] = {
{ GPIOD, 12 },
{ GPIOD, 13 },
{ GPIOD, 14 },
{ GPIOD, 15 },
{ GPIOA, 1 },
{ GPIOA, 2 }
};
#elif defined(CJMCU) || defined(EUSTM32F103RC) || defined(NAZE) || defined(OLIMEXINO) || defined(PORT103R)
const escHardware_t escHardware[ESC_COUNT] = {
{ GPIOA, 8 },
{ GPIOA, 11 },
{ GPIOB, 6 },
{ GPIOB, 7 },
{ GPIOB, 8 },
{ GPIOB, 9 }
};
#elif CC3D
const escHardware_t escHardware[ESC_COUNT] = {
{ GPIOB, 9 },
{ GPIOB, 8 },
{ GPIOB, 7 },
{ GPIOA, 8 },
{ GPIOB, 4 },
{ GPIOA, 2 }
};
#endif

static void gpio_set_mode(GPIO_TypeDef* gpio, uint16_t pin, GPIO_Mode mode) {
gpio_config_t cfg;
cfg.pin = pin;
cfg.mode = mode;
cfg.speed = Speed_10MHz;
gpioInit(gpio, &cfg);
}

#ifdef STM32F10X
static volatile uint32_t original_cr_mask, in_cr_mask, out_cr_mask;
static __IO uint32_t *cr;
static void gpio_prep_vars(uint16_t escIndex)
{
GPIO_TypeDef *gpio = escHardware[escIndex].gpio;
uint32_t pinpos = escHardware[escIndex].pinpos;
// mask out extra bits from pinmode, leaving just CNF+MODE
uint32_t inmode = Mode_IPU & 0x0F;
uint32_t outmode = (Mode_Out_PP & 0x0F) | Speed_10MHz;
// reference CRL or CRH, depending whether pin number is 0..7 or 8..15
cr = &gpio->CRL + (pinpos / 8);
// offset to CNF and MODE portions of CRx register
uint32_t shift = (pinpos % 8) * 4;
// Read out current CRx value
original_cr_mask = in_cr_mask = out_cr_mask = *cr;
// Mask out 4 bits
in_cr_mask &= ~(0xF << shift);
out_cr_mask &= ~(0xF << shift);
// save current pinmode
in_cr_mask |= inmode << shift;
out_cr_mask |= outmode << shift;
}

static void gpioSetOne(uint16_t escIndex, GPIO_Mode mode) {
// reference CRL or CRH, depending whether pin number is 0..7 or 8..15
if (mode == Mode_IPU) {
*cr = in_cr_mask;
escHardware[escIndex].gpio->ODR |= (1U << escHardware[escIndex].pinpos);
}
else {
*cr = out_cr_mask;
}
}
#endif

#define disable_hardware_uart __disable_irq()
#define enable_hardware_uart __enable_irq()
#define ESC_HI(escIndex) ((escHardware[escIndex].gpio->IDR & (1U << escHardware[escIndex].pinpos)) != (uint32_t)Bit_RESET)
#define RX_HI ((S1W_RX_GPIO->IDR & S1W_RX_PIN) != (uint32_t)Bit_RESET)
#define ESC_SET_HI(escIndex) escHardware[escIndex].gpio->BSRR = (1U << escHardware[escIndex].pinpos)
#define ESC_SET_LO(escIndex) escHardware[escIndex].gpio->BRR = (1U << escHardware[escIndex].pinpos)
#define TX_SET_HIGH S1W_TX_GPIO->BSRR = S1W_TX_PIN
#define TX_SET_LO S1W_TX_GPIO->BRR = S1W_TX_PIN

#ifdef STM32F303xC
#define ESC_INPUT(escIndex) escHardware[escIndex].gpio->MODER &= ~(GPIO_MODER_MODER0 << (escHardware[escIndex].pinpos * 2))
#define ESC_OUTPUT(escIndex) escHardware[escIndex].gpio->MODER |= GPIO_Mode_OUT << (escHardware[escIndex].pinpos * 2)
#endif

#ifdef STM32F10X
#define ESC_INPUT(escIndex) gpioSetOne(escIndex, Mode_IPU)
#define ESC_OUTPUT(escIndex) gpioSetOne(escIndex, Mode_Out_PP)
#endif

#if defined(STM32F3DISCOVERY)
// Top Left LD4, PE8 (blue)-- from programmer (RX)
#define RX_LED_OFF GPIOE->BRR = GPIO_Pin_8
#define RX_LED_ON GPIOE->BSRR = GPIO_Pin_8
// Top Right LD5, PE10 (orange) -- to programmer (TX)
#define TX_LED_OFF GPIOE->BRR = GPIO_Pin_10
#define TX_LED_ON GPIOE->BSRR = GPIO_Pin_10
static void ledInitDebug(void)
{
uint32_t pinmask = LED_PRGMR_RX|LED_PRGMR_TX;
GPIO_DeInit(GPIOE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOE, ENABLE);
gpio_set_mode(GPIOE, pinmask, Mode_Out_PP);
GPIOE->BRR = pinmask;
}
#else
#define RX_LED_OFF LED0_OFF
#define RX_LED_ON LED0_ON
#define TX_LED_OFF LED1_OFF
#define TX_LED_ON LED1_ON
#endif

// This method translates 2 wires (a tx and rx line) to 1 wire, by letting the
// RX line control when data should be read or written from the single line
void usb1WirePassthrough(int8_t escIndex)
{
#ifdef STM32F3DISCOVERY
ledInitDebug();
#endif

//Disable all interrupts
disable_hardware_uart;

//Turn off the inverter, if necessary
#if defined(INVERTER) && defined(SERIAL_1WIRE_USE_MAIN)
INVERTER_OFF;
#endif

// reset all the pins
GPIO_ResetBits(S1W_RX_GPIO, S1W_RX_PIN);
GPIO_ResetBits(S1W_TX_GPIO, S1W_TX_PIN);
GPIO_ResetBits(escHardware[escIndex].gpio, (1U << escHardware[escIndex].pinpos));
// configure gpio
gpio_set_mode(S1W_RX_GPIO, S1W_RX_PIN, Mode_IPU);
gpio_set_mode(S1W_TX_GPIO, S1W_TX_PIN, Mode_Out_PP);
gpio_set_mode(escHardware[escIndex].gpio, (1U << escHardware[escIndex].pinpos), Mode_IPU);
// hey user, turn on your ESC now

#ifdef STM32F10X
// reset our gpio register pointers and bitmask values
gpio_prep_vars(escIndex);
#endif

// Wait for programmer to go from 1 -> 0 indicating incoming data
while(RX_HI);
while(1) {
// A new iteration on this loop starts when we have data from the programmer (read_programmer goes low)
// Setup escIndex pin to send data, pullup is the default
ESC_OUTPUT(escIndex);
// Write the first bit
ESC_SET_LO(escIndex);
// Echo on the programmer tx line
TX_SET_LO;
//set LEDs
RX_LED_OFF;
TX_LED_ON;
// Wait for programmer to go 0 -> 1
uint32_t ct=3000;
while(!RX_HI) {
ct--;
if (ct==0) {
// Programmer RX -- unneeded as we explicity set this mode above
// gpio_set_mode(S1W_RX_GPIO, S1W_RX_PIN, Mode_IPU);
// Programmer TX
gpio_set_mode(S1W_TX_GPIO, S1W_TX_PIN, Mode_AF_PP);
#ifdef STM32F10X
*cr = original_cr_mask;
#endif
#if defined(INVERTER) && defined(SERIAL_1WIRE_USE_MAIN)
INVERTER_ON;
#endif
// Enable Hardware UART
enable_hardware_uart;
return;
}
}
// Programmer is high, end of bit
// Echo to the esc
ESC_SET_HI(escIndex);
// Listen to the escIndex, input mode, pullup resistor is on
ESC_INPUT(escIndex);
TX_LED_OFF;
// Listen to the escIndex while there is no data from the programmer
while (RX_HI) {
if (ESC_HI(escIndex)) {
TX_SET_HIGH;
RX_LED_OFF;
}
else {
TX_SET_LO;
RX_LED_ON;
}
}
}
}

#endif
31 changes: 31 additions & 0 deletions src/main/io/serial_1wire.h
@@ -0,0 +1,31 @@
/*
* This file is part of Cleanflight.
*
* Cleanflight is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Cleanflight is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Cleanflight. If not, see <http://www.gnu.org/licenses/>.
*
* Ported from https://github.com/4712/BLHeliSuite/blob/master/Interfaces/Arduino1Wire/Source/Arduino1Wire_C/Arduino1Wire.c
* by Nathan Tsoi <nathan@vertile.com>
*/

#pragma once

#ifdef USE_SERIAL_1WIRE

typedef struct {
GPIO_TypeDef* gpio;
uint32_t pinpos;
} escHardware_t;

void usb1WirePassthrough(int8_t escIndex);
#endif

0 comments on commit d374391

Please sign in to comment.