Compare commits

...

2 commits

Author SHA1 Message Date
3126e777bf autohuttli: add the CH32V003 I2C interceptor
All checks were successful
continuous-integration/drone/push Build is passing
2024-10-20 14:34:54 +00:00
90b0c24d36 autohuttli: auto update is working, so switch to a slow interval 2024-10-19 16:36:01 +00:00
8 changed files with 227 additions and 1 deletions

2
.gitignore vendored
View file

@ -3,3 +3,5 @@ secrets.yaml
.esphome/
dist/
__pycache__/
build/
tmp.*

View file

@ -39,7 +39,7 @@ update:
- platform: http_request
id: auto_update
source: "https://juju.nz/michaelh/assets/esphome/${project_name}.json"
update_interval: 2min
update_interval: 4hours
interval:
- interval: 50s

View file

@ -0,0 +1,6 @@
cmake_minimum_required(VERSION 3.13.1)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(app LANGUAGES CXX)
target_sources(app PRIVATE src/main.cc)

8
interceptor/README.md Normal file
View file

@ -0,0 +1,8 @@
# Interceptor
A passive digital signal sampler for the CH32V003. Tracks the levels of PC1 and PC2 and sends any
changes over serial to the host. These can be converted to a VCD format file and analyzed in
sigrock.
Designed for sniffing I2C traffic. PC1 and PC2 are 5 V tollerant and have pullups enabled. Tested at
up to 200 kHz.

View file

@ -0,0 +1,48 @@
#include <zephyr/dt-bindings/pinctrl/ch32v003-pinctrl.h>
/ {
chosen {
zephyr,console = &usart1;
zephyr,shell-uart = &usart1;
};
leds {
compatible = "gpio-leds";
red_led: led0 {
gpios = <&gpioa 1 GPIO_ACTIVE_HIGH>;
};
scl_pin: scl_pin {
gpios = <&gpioc 2 GPIO_PULL_UP>;
};
sda_pin: sda_pin {
gpios = <&gpioc 1 GPIO_PULL_UP>;
};
};
aliases {
led0 = &red_led;
scl = &scl_pin;
sda = &sda_pin;
};
};
&pinctrl {
};
&gpioa {
status = "okay";
};
&gpioc {
status = "okay";
};
&usart1 {
status = "okay";
current-speed = <460800>;
pinctrl-0 = <&usart1_default>;
pinctrl-names = "default";
};

25
interceptor/prj.conf Normal file
View file

@ -0,0 +1,25 @@
# Trim back the memory usage.
CONFIG_THREAD_STACK_INFO=n
CONFIG_ERRNO=n
CONFIG_CURRENT_THREAD_USE_TLS=n
CONFIG_TIMESLICING=n
CONFIG_KERNEL_MEM_POOL=n
CONFIG_COMMON_LIBC_MALLOC=n
CONFIG_MINIMAL_LIBC=y
CONFIG_CBPRINTF_NANO=y
CONFIG_CBPRINTF_REDUCED_INTEGRAL=y
CONFIG_CBPRINTF_N_SPECIFIER=n
CONFIG_WCH_CH32V00X_HSI=y
CONFIG_WCH_CH32V00X_HSE=n
CONFIG_WCH_CH32V00X_HSI_AS_PLLSRC=y
CONFIG_GPIO=y
CONFIG_SERIAL=y
CONFIG_CONSOLE=y
CONFIG_UART_CONSOLE=y
CONFIG_CPP=y
CONFIG_STD_CPP2B=y
CONFIG_MAIN_STACK_SIZE=1024

88
interceptor/src/main.cc Normal file
View file

@ -0,0 +1,88 @@
/*
* Copyright (c) 2024 Google LLC.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/drivers/gpio.h>
#include <zephyr/kernel.h>
#include <ch32_gpio.h>
#include <ch32_uart.h>
namespace
{
constexpr struct gpio_dt_spec kLed = GPIO_DT_SPEC_GET(DT_ALIAS(led0), gpios);
constexpr struct gpio_dt_spec kScl = GPIO_DT_SPEC_GET(DT_ALIAS(scl), gpios);
constexpr struct gpio_dt_spec kSda = GPIO_DT_SPEC_GET(DT_ALIAS(sda), gpios);
void sniff()
{
GPIO_TypeDef *port = GPIOC;
USART_TypeDef *uart = USART1;
uint8_t fifo[256];
int head = 0;
int tail = 0;
// Note that using `int` reduces the code size by removing unneeded truncation.
int samples = 0;
int sample_count = 0;
int last_sample = 0;
int unchanged_for = 0;
csr_clear(mstatus, MSTATUS_IEN);
for (;;) {
static_assert(kScl.port == kSda.port);
static_assert(kScl.pin == kSda.pin + 1);
int sample = (port->INDR >> kSda.pin) & 3;
// Record when the value changes, or the value has been unchanged for some time.
if (sample != last_sample || ++unchanged_for >= 1024) {
samples <<= 2;
samples |= sample;
last_sample = sample;
unchanged_for = 0;
if (++sample_count >= 3) {
// Encode the three samples into valid ASCII.
fifo[head++] = samples + ' ';
head %= sizeof(fifo);
samples = 0;
sample_count = 0;
}
}
if (tail != head && (uart->STATR & USART_STATR_TXE) != 0) {
uart->DATAR = fifo[tail++];
tail %= sizeof(fifo);
if (tail == 0 && (uart->STATR & USART_STATR_RXNE) != 0 &&
uart->DATAR == '~') {
break;
}
}
}
csr_set(mstatus, MSTATUS_IEN);
}
void interceptor()
{
gpio_pin_configure_dt(&kLed, GPIO_OUTPUT_ACTIVE);
gpio_pin_configure_dt(&kScl, GPIO_INPUT);
gpio_pin_configure_dt(&kSda, GPIO_INPUT);
for (;;) {
printk("sniff\n");
sniff();
}
}
} // namespace
int main(void)
{
interceptor();
return 0;
}

49
interceptor/tovcd.py Normal file
View file

@ -0,0 +1,49 @@
"""Converts the samples from the board to a VCD file for sigrok.
Usage:
python3 tovcd.py < /dev/ttyUSB0 | tee log.vcd
"""
import datetime
import sys
_HEADER = """
$date {date} $end
$version juju.nz tovcd.py $end
$timescale 1 us $end
$scope module libsigrok $end
"""
_MID = """
$upscope $end
$enddefinitions $end
"""
_IDS = "!#$%&'()"
def main():
print(_HEADER.strip().format(date=datetime.datetime.now()))
for wire in range(2):
print(f"$var wire 1 {_IDS[wire]} D{wire} $end")
print(_MID.strip())
now = 0
last_d = None
for ch in sys.stdin.read():
ch = ord(ch) - 32
for i in range(3):
now += 1
d = (ch >> 5) & 1, (ch >> 4) & 1
ch <<= 2
if d != last_d:
parts = (f"{x}{_IDS[i]}" for i, x in enumerate(d))
print(f"#{now}", " ".join(parts))
last_d = d
if __name__ == "__main__":
main()