diff --git a/F0:F030,F042,F072/usbcan_gpio/Readme.md b/F0:F030,F042,F072/usbcan_gpio/Readme.md index 5a88ded..ca166d9 100644 --- a/F0:F030,F042,F072/usbcan_gpio/Readme.md +++ b/F0:F030,F042,F072/usbcan_gpio/Readme.md @@ -1,20 +1,406 @@ -USB-CAN adapter -=============== +# USB-CAN-GPIO Adapter Command Protocol Based on [USB-CAN](https://github.com/eddyem/stm32samples/tree/master/F0%3AF030%2CF042%2CF072/usbcan_ringbuffer). Unlike original (only USB-CAN) adapter, in spite of it have GPIO contacts on board, this firmware allows to use both USB-CAN and GPIO. -The old USB-CAN is available as earlier by /dev/USB-CANx, also you can see new device: /dev/USB-GPIOx. -New interface allows you to configure GPIO and use it's base functions: in/out/ADC. +The old USB-CAN is available as earlier by /dev/USB-CANx, also you can see new device: /dev/USB-GPIOx. +New interface allows you to configure GPIO and use it's base functions. +--- -## DMA channels +# GPIO Interface Protocol for `/dev/USB-CANx` -DMA1 channel 1: ADC +--- -DMA1 channel 4: USART[1,2]_TX +## Overview +This firmware implements a versatile USB device with two CDC ACM interfaces: +- **ICAN** (interface 0) — for CAN bus communication +- **IGPIO** (interface 1) — for GPIO, USART, I2C, SPI, PWM control and monitoring -DMA1 channel 5: USART[1,2]_RX +The device can be configured dynamically via text commands sent over the USB virtual COM ports. All settings can be saved to internal flash and restored on startup. +--- + +## General Command Format +- Commands are case-sensitive, up to 15 characters long. +- Parameters are separated by spaces or commas. +- Numeric values can be decimal, hexadecimal (`0x...`), octal (`0...`), or binary (`b...`). +- For `USART=...` command the part after `=` is interpreted according to the current `hexinput` mode (see `hexinput` command). + Specific numeric values (as in `SPI` and `I2C` send) are hex-only without `0x` prefix. + +--- + +## Global Commands +| Command | Description | +|---------|-------------| +| `canspeed [=value]` | Get or set CAN bus speed (kBaud, 10─1000). | +| `curcanspeed` | Show the actual CAN speed currently used by the interface. | +| `curpinconf` | Dump the *current* (working) pin configuration ─ may differ from saved config. | +| `dumpconf` | Dump the global configuration stored in RAM (including CAN speed, interface names, pin settings, USART/I2C/SPI parameters). | +| `eraseflash` | Erase the entire user configuration storage area. | +| `help` | Show help message. | +| `hexinput [=0/1]` | Set input mode: `0` ─ plain text, `1` ─ hex bytes + quoted text. Affects commands like `USART` and `sendcan`. | +| `iic=addr data...` | Write data over I2C. Address and data bytes are given in hex. Example: `iic=50 01 02` | +| `iicread=addr nbytes` | Read `nbytes` from I2C device at `addr` and display as hex. Both addr and nbytes are hex numbers. | +| `iicreadreg=addr reg nbytes` | Read `nbytes` from I2C device register `reg`. Also hex. | +| `iicscan` | Start scanning I2C bus; found addresses are printed asynchronously. | +| `mcutemp` | Show MCU internal temperature in ℃*10. | +| `mcureset` | Reset the microcontroller. | +| `PAx [= ...]` | Configure or read GPIOA pin `x` (0-15). See **Pin Configuration** below. | +| `PBx [= ...]` | Configure or read GPIOB pin `x`. | +| `pinout [=function1,function2,...]` | List all pins and their available alternate functions. If a space- or comma-separated list of functions is given, only pins supporting any of them are shown. | +| `pwmmap` | Show pins capable of PWM and indicate collisions (pins sharing the same timer channel). | +| `readconf` | Re-read configuration from flash into RAM. | +| `reinit` | Apply the current pin configuration to hardware. Must be issued after any pin changes. | +| `saveconf` | Save current RAM configuration to flash. | +| `sendcan=...` | Send data over the CAN USB interface. If `hexinput=1`, the argument is parsed as hex bytes with plain text in quotes; otherwise as plain text (a newline is appended). | +| `setiface=N [=name]` | Set or get the name of interface `N` (0 = CAN, 1 = GPIO). | +| `SPI=data...` | Perform SPI transfer: send hex bytes, receive and display the received bytes. | +| `time` | Show system time in milliseconds since start. | +| `USART[=...]` | If `=...` is given, send data over USART (text or hex depending on `hexinput`). Without argument, read and display any received USART data (as text or hex, according to USART's `TEXT`/`HEX` setting). | +| `vdd` | Show approximate supply voltage (Vdd, V*100). | + +--- + +## Pin Configuration (Commands `PAx`, `PBx`) + +Pins are configured with the syntax: +``` +PAx = MODE PULL OTYPE FUNC MISC ... +``` +or for a simple digital output: +``` +PAx = 0 # set pin low +PAx = 1 # set pin high +``` +or for PWM output: +``` +PAx = value # set PWM duty cycle (0-255) +``` + +### Available Keywords (in any order) + +| Group | Keyword | Meaning | +|---------|----------|---------| +| **MODE** | `AIN` | Analog input (ADC) | +| | `IN` | Digital input | +| | `OUT` | Digital output | +| | `AF` | Alternate function ─ automatically set when a function like `USART`, `SPI`, `I2C`, `PWM` is selected. | +| **PULL** | `PU` | Pull-up enabled (GPIO-only group, don't affect on functions) | +| | `PD` | Pull-down enabled | +| | `FL` | Floating (no pull) | +| **OTYPE**| `PP` | Push-pull output | +| | `OD` | Open-drain output | +| **FUNC** | `USART` | Enable USART alternate function | +| | `SPI` | Enable SPI alternate function | +| | `I2C` | Enable I2C alternate function | +| | `PWM` | Enable PWM alternate function | +| **MISC** | `MONITOR`| Enable asynchronous monitoring of pin changes (for GPIO input or ADC) or USART incoming message. When the pin changes, its new value is sent over USB automatically. | +| | `THRESHOLD n` | Set ADC threshold (0-4095). Only meaningful with `AIN`. A change larger than this triggers an async report (if `MONITOR` enabled on this pin). | +| | `SPEED n` | Set interface speed/frequency (baud for USART, Hz for SPI, speed index for I2C). | +| | `TEXT` | USART operates in text mode (lines terminated by `\n`). | +| | `HEX` | USART operates in binary mode (data output as hex dump, data input by IDLE-separated portions if `MONITOR` enabled). | + +### Notes +- `AF` is automatically set when any `FUNC` is selected; you do not need to type it explicitly. +- For `PWM`, the duty cycle can be set by assigning a number (0-255) directly, e.g. `PA1=128`. +- For `OUT`, assigning `0` or `1` sets/clears the pin. +- For `ADC` (`AIN`), `MONITOR` uses the `THRESHOLD` value; if not given, any change triggers a report. +- Conflicting configurations (e.g., two different USARTs on the same pins, or missing SCL/SDA for I2C) are detected and rejected with an error message. + +After changing pin settings, you must issue the `reinit` command for the changes to take effect. + +--- + +## USART + +### Pin Assignment +USART1 can be used on: +- PA9 (TX), PA10 (RX) +- PB6 (TX), PB7 (RX) + +USART2 on: +- PA2 (TX), PA3 (RX) + +Both USARTs share the same DMA channels, so only one USART can be active at a time. + +### Configuration via Pin Commands +When you set a pin to `FUNC_USART`, you can also specify: +- `SPEED n` ─ baud rate (default 9600) +- `TEXT` ─ enable line-oriented mode (data is buffered until `\n`) +- `HEX` ─ binary mode (data is sent/received as raw bytes; asynchronous output appears as hex dump) +- `MONITOR` ─ enable asynchronous reception; received data will be sent over USB automatically (as text lines or hex dump depending on mode) + +Example: +``` +PA9 = USART SPEED 115200 TEXT MONITOR +PA10 = USART +reinit +``` +Now USART1 is active at 115200 baud, text mode, with monitoring. Any incoming line will be printed as `USART = ...`. + +### Sending Data +``` +USART=Hello, world! # if hexinput=0, sends plain text +USART=48 65 6c 6c 6f # if hexinput=1, sends 5 bytes +``` +If `TEXT` mode is enabled, a newline is automatically appended to the transmitted string. + +### Receiving Data +Without `=`, the command reads and displays any data waiting in the ring buffer, like: +``` +USART = 48 65 6c 6c 6f +``` +If in `TEXT` mode, only complete lines are returned. In `HEX` mode, all received bytes are shown as a hex dump. + +If `MONITOR` disabled, but incoming data flow is too large for buffering between consequent `USART` calls, +some "old" data would be printed asynchroneously. + +--- + +## I2C + +### Pin Assignment + +I2C1 is available on any mix of: +- PB6 (SCL), PB7 (SDA) +- PB10 (SCL), PB11 (SDA) + +You must configure both SCL and SDA pins for I2C. + +### Configuration via Pin Commands +- `I2C` ─ selects I2C alternate function +- `SPEED n` ─ speed index: 0=10kHz, 1=100kHz, 2=400kHz, 3=1MHz (default 100kHz if omitted) +- `MONITOR` is not used for I2C. + +Example: +``` +PB6 = I2C SPEED 2 +PB7 = I2C +reinit +``` +This sets up I2C at 400 kHz. + +### Commands +- `iic=addr data...` ─ write bytes to device at 7-bit address `addr`. Address and data are given in hex, e.g. `iic=50 01 02 03`. +- `iicread=addr nbytes` ─ read `nbytes` (both numbers are hex) from device and display as hex dump. +- `iicreadreg=addr reg nbytes` ─ read `nbytes` from register `reg` (all numbers are hex). +- `iicscan` ─ start scanning all possible 7-bit addresses (1─127). As devices respond, messages like `foundaddr = 0x50` are printed asynchronously. + +--- + +## SPI + +### Pin Assignment + +SPI1 can be used on any mix of: +- **PA5** (SCK), **PA6** (MISO), **PA7** (MOSI) +- **PB3** (SCK), **PB4** (MISO), **PB5** (MOSI) + +All three pins are not strictly required; you may configure only SCK+MOSI (TX only) or SCK+MISO (RX only). The SPI peripheral will be set to the appropriate mode automatically. + +### Configuration via Pin Commands +- `SPI` ─ selects SPI alternate function +- `SPEED n` ─ desired frequency in Hz (actual nearest prescaler will be chosen automatically) +- `CPOL` ─ clock polarity (1 = idle high) +- `CPHA` ─ clock phase (1 = second edge) +- `LSBFIRST` ─ LSB first transmission. + +If `CPOL`/`CPHA` are not given, they default to 0 (mode 0). `LSBFIRST` defaults to MSB first. + +Example: +``` +PA5 = SPI SPEED 2000000 CPOL CPHA +PA6 = SPI +PA7 = SPI +reinit +``` +Configures SPI at ~2 MHz, mode 3 (CPOL=1, CPHA=1), full duplex. + +### Commands +- `SPI=data...` ─ send the given hex bytes, and display the received bytes. Works in full-duplex or write-only modes. Example: + ``` + SPI=01 02 03 + ``` + will send three bytes and output the three bytes received simultaneously. + +- `SPI=n` — reads `n` bytest of data (n should be decimal, binary or hex with prefixes `0b` or `0x`). + +--- + +## PWM + +### Pin Assignment +PWM is available on many pins; see the output of `pwmmap` (or `pinout=PWM`) for a complete list with possible conflicts (pins sharing the same timer channel). +Conflicts are automatically detected ─ if you try to use two conflicting pins, one will be reset to default. + +### Configuration via Pin Commands +- `PWM` ─ selects PWM alternate function +- No additional parameters are needed; the duty cycle is set by writing a number directly to the pin. + +Example: +``` +PA1 = PWM +reinit +PA1=128 # set 50% duty cycle +``` + +### Reading PWM Value +``` +PA1 +``` +returns the current duty cycle (0─255). + +--- + +## Monitoring and Asynchronous Messages + +When a pin is configured with `MONITOR` and is not in AF mode (i.e., GPIO input or ADC), any change in its state triggers an automatic USB message. The format is the same as the pin getter: `PAx = value`. For ADC, the value is the ADC reading; the message is sent only when the change exceeds the programmed `THRESHOLD` (if any). + +USART monitoring (if enabled with `MONITOR`) sends received data asynchronously, using the same output format as the `USART` command. + +I2C scan results are also printed asynchronously while scan mode is active. + +--- + +## Saving Configuration + +The entire configuration (interface names, CAN speed, pin settings, USART/I2C/SPI parameters) can be saved to flash with `saveconf`. +On startup, the last saved configuration is automatically loaded. The flash storage uses a simple rotating scheme, so many previous configurations are preserved until the storage area is erased with `eraseflash`. + +--- + +## Error Codes +Most commands return one of the following status strings (if not silent): + +| Code | Meaning | +|-------------|---------| +| `OK` | Command executed successfully. | +| `BADCMD` | Unknown command. | +| `BADPAR` | Invalid parameter. | +| `BADVAL` | Value out of range or unacceptable. | +| `WRONGLEN` | Message length incorrect. | +| `CANTRUN` | Cannot execute due to current state (e.g., peripheral not configured). | +| `BUSY` | Resource busy (e.g., USART TX busy). | +| `OVERFLOW` | Input string too long. | + +Commands that produce no direct output (e.g., getters) return nothing (silent) and only print the requested value. + +--- + +# CAN Interface Protocol for `/dev/USB-CANx` + +--- + +This part describes the simple text‑based protocol used to communicate with the CAN interface of the USB‑CAN‑GPIO adapter. +The interface appears as a standard CDC ACM virtual COM port (e.g. `/dev/ttyACM0` or `/dev/USB-CANx`). +All commands are terminated by a newline character (`\n`). Numeric parameters can be entered in decimal, hexadecimal (prefix `0x`), octal (prefix `0`), or binary (prefix `b`). + +--- + +## Received CAN Message Format + +When a CAN message is received (and display is not paused), it is printed as: + +``` + # ... +``` + +- **``** – system time in milliseconds since start. +- **``** – 11‑bit CAN identifier in hexadecimal (e.g. `0x123`). +- **``** – data bytes in hexadecimal, separated by spaces. If the message has no data, only the timestamp and ID are printed. + +Example: +``` +12345 #0x123 0xDE 0xAD 0xBE 0xEF +``` + +--- + +## Commands + +### General +| Command | Description | Example | +|---------|-------------|---------| +| `?` | Show a brief help message. | `?` | + +### CAN Configuration & Control +| Command | Description | Example | +|---------|-------------|---------| +| `b [speed]` | Set CAN bus speed in kBaud (10–1000). Without argument, display the current speed. | `b 250` | +| `I` | Re‑initialise the CAN controller with the last set speed. | `I` | +| `c` | Show CAN status registers (MSR, TSR, RF0R, RF1R). | `c` | +| `e` | Show the CAN error register (ESR) with a human‑readable description. | `e` | + +### Sending Messages +| Command | Description | Example | +|---------|-------------|---------| +| `s ID [byte0 ... byte7]` | Send a CAN message with the given ID and up to 8 data bytes. Returns immediately after queuing. | `s 0x123 0xAA 0xBB` | +| `S ID [byte0 ... byte7]` | Same as `s`. | `S 0x100 0x01` | + +### Flood (Periodic Transmission) +| Command | Description | Example | +|---------|-------------|---------| +| `F ID [byte0 ... byte7]` | Set a message to be transmitted repeatedly. The message is stored and sent every `t` milliseconds. | `F 0x200 0x55 0xAA` | +| `i` | Enable *incremental* flood mode. Sends a 4‑byte counter (increasing by 1 each time) using the ID from the last `F` command. | `i` | +| `t ` | Set the flood period in milliseconds (default 5 ms). | `t 10` | + +### Filtering +| Command | Description | Example | +|---------|-------------|---------| +| `f bank fifo mode num0 [num1 [num2 [num3]]]` | Configure a hardware filter. `bank` — filter bank number (0–27). `fifo` — FIFO assignment (0 or 1). `mode` — `I` for ID list mode, `M` for mask mode. `numX` — IDs or ID/mask pairs (for mask mode). | `f 0 1 I 0x123 0x456` – two IDs in list mode. `f 1 0 M 0x123 0x7FF` – ID 0x123 with mask 0x7FF (accept all). | +| `l` | List all active filters with their configuration. | `l` | +| `a ` | Add an ID to the software ignore list (up to 10 IDs). Messages with this ID will not be printed. | `a 0x321` | +| `p` | Print the current ignore list. | `p` | +| `d` | Clear the entire ignore list. | `d` | +| `P` | Pause/resume printing of incoming CAN messages. Toggles between paused and running. | `P` | + +### LEDs & Misc +| Command | Description | Example | +|---------|-------------|---------| +| `o` | Turn both LEDs off. | `o` | +| `O` | Turn both LEDs on. | `O` | +| `T` | Show the system time in milliseconds since start. | `T` | +| `R` | Perform a software reset of the microcontroller. | `R` | + +--- + +## Error Reporting + +When an error occurs, the device may print one of the following messages: + +| Message | Meaning | +|-----------------|---------| +| `ERROR: ...` | General error with a descriptive text. | +| `NAK` | A transmitted message was not acknowledged (e.g., no receiver on the bus). | +| `FIFO overrun` | A CAN FIFO overrun occurred – messages were lost. | +| `CAN bus is off`| The controller entered the bus‑off state; try to restart it with `I`. | + +Additionally, the `e` command gives a detailed breakdown of the error register. + +--- + +## Notes + +- All settings (baud rate, filters, ignore list, flood message) are stored in RAM only and are lost after a reset or power‑off. +To make changes (only speed available) permanent, use the **GPIO interface** commands `saveconf`/`storeconf` after configuring CAN. +- The device can only handle one active USART at a time, but the CAN interface operates independently. +- The command parser is case‑sensitive for the single‑letter commands (they are expected in lower case, except where noted). + +--- + +## How to distinguish between identical device + +In the **GPIO interface** you can setup custom interface name (`setiface=...`) for both USB-CAN and USB-GPIO interfaces. +After storing them in flash, reconnect and `lsusb -v` for given device will show your saved names on `iInterface` fields. +These fields could be used to create human-readable symlinks in `/dev`. + +To see symlink in `/dev` to your `/dev/ttyACMx` device based on `iInterface` field, add this udev-script to `/etc/udev/rules.d/usbids.rules` + +``` +ACTION=="add", DRIVERS=="usb", ENV{USB_IDS}="%s{idVendor}:%s{idProduct}" +ACTION=="add", ENV{USB_IDS}=="067b:2303", ATTRS{interface}=="?*", PROGRAM="/bin/bash -c \"ls /dev | grep $attr{interface} | wc -l \"", SYMLINK+="$attr{interface}%c", MODE="0666", GROUP="tty" +ACTION=="add", ENV{USB_IDS}=="0483:5740", ATTRS{interface}=="?*", PROGRAM="/bin/bash -c \"ls /dev | grep $attr{interface} | wc -l \"", SYMLINK+="$attr{interface}%c", MODE="0666", GROUP="tty" +``` diff --git a/F0:F030,F042,F072/usbcan_gpio/flash.c b/F0:F030,F042,F072/usbcan_gpio/flash.c index f1e5b73..ee1d11f 100644 --- a/F0:F030,F042,F072/usbcan_gpio/flash.c +++ b/F0:F030,F042,F072/usbcan_gpio/flash.c @@ -44,6 +44,7 @@ const user_conf *Flash_Data = (const user_conf *)(&__varsstart); #define PINEN {.enable = 1} // USART1 @9600 with monitoring #define U1 {.enable = 1, .mode = MODE_AF, .speed = SPEED_HIGH, .afno = 1, .af = FUNC_USART, .monitor = 1} +#define S1 {.enable = 1, .mode = MODE_AF, .speed = SPEED_HIGH, .afno = 0, .af = FUNC_SPI} // GPIOA, enabled: PA0-PA3, PA5-PA7, PA9, PA10 #define PACONF \ [0] = PINEN, [1] = PINEN, [2] = PINEN, [3] = PINEN, [5] = PINEN, \ @@ -51,8 +52,8 @@ const user_conf *Flash_Data = (const user_conf *)(&__varsstart); // GPIOB, enabled: PB0-PB7, PB10, PB11 #define PBCONF \ -[0] = PINEN, [1] = PINEN, [2] = PINEN, [3] = PINEN, [4] = PINEN, \ -[5] = PINEN, [6] = PINEN, [7] = PINEN, [10] = PINEN, [11] = PINEN +[0] = PINEN, [1] = PINEN, [2] = PINEN, [3] = S1, [4] = S1, \ +[5] = S1, [6] = PINEN, [7] = PINEN, [10] = PINEN, [11] = PINEN user_conf the_conf = { .userconf_sz = sizeof(user_conf), @@ -64,6 +65,7 @@ user_conf the_conf = { .iIlengths = {14, 16}, .pinconfig = {[0] = {PACONF}, [1] = {PBCONF}}, .usartconfig = {.speed = 9600, .idx = 0, .RXen = 1, .TXen = 1, .textproto = 1, .monitor = 1}, + .spiconfig = {.speed = 1000000, .cpol = 1, .cpha = 1}, }; int currentconfidx = -1; // index of current configuration @@ -196,3 +198,7 @@ static int erase_flash(const void *start, const void *end){ int erase_storage(){ return erase_flash(Flash_Data, NULL); } + +uint32_t storage_capacity(){ + return maxCnum; +} diff --git a/F0:F030,F042,F072/usbcan_gpio/flash.h b/F0:F030,F042,F072/usbcan_gpio/flash.h index a7aa4c8..8aab2e8 100644 --- a/F0:F030,F042,F072/usbcan_gpio/flash.h +++ b/F0:F030,F042,F072/usbcan_gpio/flash.h @@ -38,15 +38,25 @@ // maximal size (in letters, ASCII, no ending \0) of iInterface for settings #define MAX_IINTERFACE_SZ (16) +typedef struct { + uint32_t speed; + uint8_t cpol : 1; + uint8_t cpha : 1; + uint8_t lsbfirst : 1; + // these flags - only for data in/out formatting + uint8_t rxonly : 1; // use format SPI=len instead of SPI=data + uint8_t txonly : 1; // don't receive data +} spiconfig_t; + /* * struct to save user configurations */ typedef struct __attribute__((packed, aligned(4))){ uint16_t userconf_sz; // "magick number" uint16_t CANspeed; // default CAN speed (in kBaud!!!) - uint32_t SPIspeed; // SPI speed, baud - uint16_t iInterface[InterfacesAmount][MAX_IINTERFACE_SZ]; // we store Interface name here in UTF! + spiconfig_t spiconfig; uint8_t iIlengths[InterfacesAmount]; // length in BYTES (symbols amount x2)! + uint16_t iInterface[InterfacesAmount][MAX_IINTERFACE_SZ]; // we store Interface name here in UTF! // gpio settings pinconfig_t pinconfig[2][16]; // GPIOA, GPIOB usartconf_t usartconfig; @@ -62,3 +72,4 @@ void flashstorage_init(); int store_userconf(); void dump_userconf(); int erase_storage(); +uint32_t storage_capacity(); diff --git a/F0:F030,F042,F072/usbcan_gpio/gpio.c b/F0:F030,F042,F072/usbcan_gpio/gpio.c index 9866c07..7d32769 100644 --- a/F0:F030,F042,F072/usbcan_gpio/gpio.c +++ b/F0:F030,F042,F072/usbcan_gpio/gpio.c @@ -398,6 +398,10 @@ int chkpinconf(){ if(active_spi != -1){ if(spiprops.issck && (spiprops.ismiso || spiprops.ismosi)){ haveSPI = 1; + if(!spiprops.ismosi) the_conf.spiconfig.rxonly = 1; + else the_conf.spiconfig.rxonly = 0; + if(!spiprops.ismiso) the_conf.spiconfig.txonly = 1; + else the_conf.spiconfig.txonly = 0; }else{ DBG("SPI needs SCK and MOSI or MISO\n"); ret = FALSE; @@ -551,7 +555,7 @@ int gpio_reinit(){ }else usart_stop(); if(haveI2C) i2c_setup((i2c_speed_t) the_conf.I2Cspeed); else i2c_stop(); - if(haveSPI) spi_setup(the_conf.SPIspeed); + if(haveSPI) spi_setup(); else spi_stop(); return ret; } diff --git a/F0:F030,F042,F072/usbcan_gpio/gpioproto.cpp b/F0:F030,F042,F072/usbcan_gpio/gpioproto.cpp index 2bb344f..f902421 100644 --- a/F0:F030,F042,F072/usbcan_gpio/gpioproto.cpp +++ b/F0:F030,F042,F072/usbcan_gpio/gpioproto.cpp @@ -69,8 +69,7 @@ static uint8_t hex_input_mode = 0; // ==0 for text input, 1 for HEX + text in qu COMMAND(saveconf, "save current user configuration into flash") \ COMMAND(sendcan, "send all after '=' to CAN USB interface") \ COMMAND(setiface, "set/get name of interface x (0 - CAN, 1 - GPIO)") \ - COMMAND(SPI, "transfer SPI data: SPI=data (hex)") \ - COMMAND(storeconf, "save config to flash") \ + COMMAND(SPI, "transfer SPI data: SPI=data (hex); if RXONLY: SPI = size (size to read, bytes)") \ COMMAND(time, "show current time (ms)") \ COMMAND(USART, "Read USART data or send (USART=hex)") \ COMMAND(vdd, "get approx Vdd value (V*100)") @@ -114,7 +113,10 @@ enum MiscValues{ MISC_THRESHOLD, MISC_SPEED, MISC_TEXT, - MISC_HEX + MISC_HEX, + MISC_LSBFIRST, + MISC_CPOL, + MISC_CPHA, }; // TODO: add HEX input? @@ -138,6 +140,9 @@ enum MiscValues{ KW(TEXT) \ KW(HEX) \ KW(PWM) \ + KW(LSBFIRST) \ + KW(CPOL) \ + KW(CPHA) typedef enum{ // indexes of string keywords @@ -173,6 +178,9 @@ static const Keyword keywords[] = { KEY(SPEED, GROUP_MISC, MISC_SPEED) KEY(TEXT, GROUP_MISC, MISC_TEXT) KEY(HEX, GROUP_MISC, MISC_HEX) + KEY(LSBFIRST, GROUP_MISC, MISC_LSBFIRST) + KEY(CPOL, GROUP_MISC, MISC_CPOL) + KEY(CPHA, GROUP_MISC, MISC_CPHA) #undef K }; #define NUM_KEYWORDS (sizeof(keywords)/sizeof(keywords[0])) @@ -195,10 +203,13 @@ static const char *pinhelp = " OTYPE: PP or OD (push-pull or open-drain)\n" " FUNC: USART, SPI, I2C or PWM (enable alternate function and configure peripheal)\n" " MISC: MONITOR - send data by USB as only state changed\n" - " THRESHOLD (ADC only) - monitoring threshold, ADU\n" - " SPEED - interface speed/frequency\n" + " THRESHOLD val (ADC only) - monitoring threshold, ADU\n" + " SPEED val - interface speed/frequency\n" " TEXT - USART means data as text ('\\n'-separated strings)\n" " HEX - USART means data as binary (output: HEX)\n" + " CPHA - set SPI CPHA to 1\n" + " CPOL - set SPI CPOL to 1\n" + " LCBFIRST - SPI use lsb-first proto\n" "\n" ; @@ -338,6 +349,7 @@ static errcodes_t pin_setter(uint8_t port, uint8_t pin, char *setter){ uint32_t *pending_u32 = NULL; // -//- for uint32_t uint32_t wU32 = UINT32_MAX; // for pending usartconf_t UsartConf; + spiconfig_t spiconf = {}; // for flags CPHA/CPOL/LSBFIRST if(!get_curusartconf(&UsartConf)) return ERR_CANTRUN; char *saveptr, *token = strtok_r(setter, DELIM_, &saveptr); while(token){ @@ -401,6 +413,15 @@ static errcodes_t pin_setter(uint8_t port, uint8_t pin, char *setter){ case MISC_HEX: // clear text flag UsartConf.textproto = 0; break; + case MISC_CPHA: + spiconf.cpha = 1; + break; + case MISC_CPOL: + spiconf.cpol = 1; + break; + case MISC_LSBFIRST: + spiconf.lsbfirst = 1; + break; } break; } @@ -419,11 +440,15 @@ static errcodes_t pin_setter(uint8_t port, uint8_t pin, char *setter){ if(!chkusartconf(&UsartConf)) return ERR_BADVAL; }else if(func_set == FUNC_I2C){ // check speed if(wU32 != UINT32_MAX){ - i2c_speed_t s = (wU32 > I2C_SPEED_1M) ? I2C_SPEED_10K : static_cast (wU32); + if(wU32 >= I2C_SPEED_AMOUNT) return ERR_BADVAL; + i2c_speed_t s = static_cast (wU32); the_conf.I2Cspeed = static_cast (s); } }else if(func_set == FUNC_SPI){ - if(wU32 != UINT32_MAX) the_conf.SPIspeed = wU32; + if(wU32 != UINT32_MAX) the_conf.spiconfig.speed = wU32; + the_conf.spiconfig.cpha = spiconf.cpha; + the_conf.spiconfig.cpol = spiconf.cpol; + the_conf.spiconfig.lsbfirst = spiconf.lsbfirst; } if(func_set != 0xFF) mode_set = MODE_AF; if(mode_set == 0xFF) return ERR_BADVAL; // user forgot to set mode @@ -480,11 +505,6 @@ static errcodes_t cmd_reinit(const char _U_ *cmd, char _U_ *args){ return ERR_AMOUNT; } -static errcodes_t cmd_storeconf(const char _U_ *cmd, char _U_ *args){ - if(!store_userconf()) return ERR_CANTRUN; - return ERR_OK; -} - // canspeed = baudrate (kBaud) static errcodes_t cmd_canspeed(const char *cmd, char *args){ int32_t S; @@ -575,6 +595,7 @@ static errcodes_t cmd_curpinconf(const char _U_ *cmd, char _U_ *args){ static errcodes_t cmd_dumpconf(const char _U_ *cmd, char _U_ *args){ SEND("userconf_sz="); SEND(u2str(the_conf.userconf_sz)); + SEND("\nstorage_capacity="); SEND(u2str(storage_capacity())); SEND("\ncurrentconfidx="); SENDn(i2str(currentconfidx)); for(int i = 0; i < InterfacesAmount; ++i){ SEND("interface"); PUTCHAR('0' + i); @@ -602,7 +623,7 @@ static errcodes_t cmd_dumpconf(const char _U_ *cmd, char _U_ *args){ NL(); } if(I2C1->CR1 & I2C_CR1_PE){ // I2C active, show its speed - SEND("iicspeed="); + S(I2C); SEND(EQ); S(SPEED); PUTCHAR(' '); switch(the_conf.I2Cspeed){ case 0: SEND("10kHz"); break; case 1: SEND("100kHz"); break; @@ -613,8 +634,14 @@ static errcodes_t cmd_dumpconf(const char _U_ *cmd, char _U_ *args){ NL(); } if(SPI1->CR1 & SPI_CR1_SPE){ - SEND("spispeed="); - SENDn(u2str(the_conf.SPIspeed)); + S(SPI); SEND(EQ); + S(SPEED); PUTCHAR(' '); SEND(u2str(the_conf.spiconfig.speed)); + if(the_conf.spiconfig.cpol) SP(CPOL); + if(the_conf.spiconfig.cpha) SP(CPHA); + if(the_conf.spiconfig.lsbfirst) SP(LSBFIRST); + if(the_conf.spiconfig.rxonly) SEND(" RXONLY"); + else if(the_conf.spiconfig.txonly) SEND(" TXONLY"); + NL(); } #undef S #undef SP @@ -817,7 +844,7 @@ static errcodes_t cmd_iicread(const char *cmd, char *args){ addr <<= 1; if(!i2c_read(addr, curbuf, nbytes)) return ERR_CANTRUN; CMDEQ(); - if(nbytes < 9) NL(); + if(nbytes > 8) NL(); hexdump(sendfun, curbuf, nbytes); return ERR_AMOUNT; } @@ -836,7 +863,7 @@ static errcodes_t cmd_iicreadreg(const char *cmd, char *args){ addr <<= 1; if(!i2c_read_reg(addr, nreg, curbuf, nbytes)) return ERR_CANTRUN; CMDEQ(); - if(nbytes < 9) NL(); + if(nbytes > 8) NL(); hexdump(sendfun, curbuf, nbytes); return ERR_AMOUNT; } @@ -891,16 +918,21 @@ static errcodes_t cmd_pinout(const char _U_ *cmd, char *args){ SEND((port == 0) ? "PA" : "PB"); SEND(u2str(pin)); SEND(": "); - if(listmask == 0xff) SEND("GPIO"); // don't send "GPIO" for specific choice - if(mask & (1 << FUNC_AIN)){ SEND(COMMA); SEND(str_keywords[STR_AIN]); } + int needcomma = FALSE; +#define COMMA() do{if(needcomma) SEND(COMMA); needcomma = TRUE;}while(0) + if(listmask == 0xff){ // don't send "GPIO" for specific choice + SEND("GPIO"); + needcomma = TRUE; + } + if(mask & (1 << FUNC_AIN)){ COMMA(); SEND(str_keywords[STR_AIN]); } if(mask & (1 << FUNC_USART)){ // USARTn_aX (n - 1/2, a - R/T) int idx = get_usart_index(port, pin, &up); - SEND(COMMA); SEND(str_keywords[STR_USART]); PUTCHAR('1' + idx); + COMMA(); SEND(str_keywords[STR_USART]); PUTCHAR('1' + idx); PUTCHAR('_'); PUTCHAR(up.isrx ? 'R' : 'T'); PUTCHAR('X'); } if(mask & (1 << FUNC_SPI)){ int idx = get_spi_index(port, pin, &sp); - SEND(COMMA); SEND(str_keywords[STR_SPI]); PUTCHAR('1' + idx); + COMMA(); SEND(str_keywords[STR_SPI]); PUTCHAR('1' + idx); PUTCHAR('_'); if(sp.ismiso) SEND("MISO"); else if(sp.ismosi) SEND("MOSI"); @@ -908,13 +940,13 @@ static errcodes_t cmd_pinout(const char _U_ *cmd, char *args){ } if(mask & (1 << FUNC_I2C)){ int idx = get_i2c_index(port, pin, &ip); - SEND(COMMA); SEND(str_keywords[STR_I2C]); PUTCHAR('1' + idx); + COMMA(); SEND(str_keywords[STR_I2C]); PUTCHAR('1' + idx); PUTCHAR('_'); SEND(ip.isscl ? "SCL" : "SDA"); } if(mask & (1 << FUNC_PWM)){ canPWM(port, pin, &tp); - SEND(COMMA); SEND(str_keywords[STR_PWM]); + COMMA(); SEND(str_keywords[STR_PWM]); SEND(u2str(tp.timidx)); // timidx == TIMNO! PUTCHAR('_'); PUTCHAR('1' + tp.chidx); @@ -923,6 +955,7 @@ static errcodes_t cmd_pinout(const char _U_ *cmd, char *args){ } } return ERR_AMOUNT; +#undef COMMA } static errcodes_t cmd_SPI(const char *cmd, char *args){ @@ -930,14 +963,27 @@ static errcodes_t cmd_SPI(const char *cmd, char *args){ if(!(SPI1->CR1 & SPI_CR1_SPE)) return ERR_CANTRUN; char *setter = splitargs(args, NULL); if(!setter) return ERR_BADVAL; - int len = parse_hex_data(setter, curbuf, MAXSTRLEN); + int len; + uint8_t *txbuf = curbuf, *rxbuf = curbuf; + if(the_conf.spiconfig.rxonly){ + uint32_t L; + char *nxt = getnum(setter, &L); + if(nxt == setter || L > MAXSTRLEN) return ERR_BADVAL; + len = static_cast (L); + txbuf = NULL; + }else len = parse_hex_data(setter, curbuf, MAXSTRLEN); if(len <= 0) return ERR_BADVAL; - int got = spi_transfer(curbuf, curbuf, len); + if(the_conf.spiconfig.txonly) rxbuf = NULL; + int got = spi_transfer(txbuf, rxbuf, len); if(-1 == got) return ERR_CANTRUN; if(0 == got) return ERR_BUSY; - CMDEQ(); - hexdump(sendfun, curbuf, got); - return ERR_AMOUNT; + if(!the_conf.spiconfig.txonly){ + CMDEQ(); + if(got > 8) NL(); + hexdump(sendfun, curbuf, got); + return ERR_AMOUNT; + } + return ERR_OK; } constexpr uint32_t hash(const char* str, uint32_t h = 0){ diff --git a/F0:F030,F042,F072/usbcan_gpio/pwm.c b/F0:F030,F042,F072/usbcan_gpio/pwm.c index 2b0d208..79c9aa4 100644 --- a/F0:F030,F042,F072/usbcan_gpio/pwm.c +++ b/F0:F030,F042,F072/usbcan_gpio/pwm.c @@ -126,6 +126,8 @@ int startPWM(uint8_t port, uint8_t pin){ volatile TIM_TypeDef *timer = timers[idx]; uint8_t chidx = timer_map[port][pin].chidx; uint32_t chen = TIM_CCER_CC1E << (chidx<<2); + volatile uint32_t *CCR = &timers[idx]->CCR1 + timer_map[port][pin].chidx; + *CCR = 0; // set initial value to zero if(0 == (timer->CCER & chen)){ if(0 == channel_counter[idx]++) timer->CR1 |= TIM_CR1_CEN; // start timer if need timer->CCER |= chen; // enable channel diff --git a/F0:F030,F042,F072/usbcan_gpio/spi.c b/F0:F030,F042,F072/usbcan_gpio/spi.c index 0e09c83..f6a390f 100644 --- a/F0:F030,F042,F072/usbcan_gpio/spi.c +++ b/F0:F030,F042,F072/usbcan_gpio/spi.c @@ -18,6 +18,7 @@ #include +#include "flash.h" #include "hardware.h" #include "spi.h" @@ -38,14 +39,20 @@ static uint16_t get_baudrate_prescaler(uint32_t speed_hz){ } -// Master, 8bit, CPOL=0, CPHA=0, MSB first -void spi_setup(uint32_t speed){ // speed in Hz +void spi_setup(){ RCC->APB2RSTR |= RCC_APB2RSTR_SPI1RST; RCC->APB2RSTR = 0; SPI1->CR1 = 0; - uint16_t br = get_baudrate_prescaler(speed); - SPI1->CR1 = SPI_CR1_MSTR | (br << 3) | SPI_CR1_SSM | SPI_CR1_SSI; - SPI1->CR2 = SPI_CR2_SSOE; + uint16_t br = get_baudrate_prescaler(the_conf.spiconfig.speed); + uint32_t cr1 = SPI_CR1_MSTR | (br << 3) | SPI_CR1_SSM | SPI_CR1_SSI; + if(the_conf.spiconfig.cpol) cr1 |= SPI_CR1_CPOL; + if(the_conf.spiconfig.cpha) cr1 |= SPI_CR1_CPHA; + if(the_conf.spiconfig.lsbfirst) cr1 |= SPI_CR1_LSBFIRST; + // there would be a lot of problem to set rxonly! + //if(the_conf.spiconfig.rxonly) cr1 |= SPI_CR1_RXONLY; + SPI1->CR1 = cr1; + // rxne after 8bits, ds 8bit + SPI1->CR2 = /*SPI_CR2_SSOE | */ SPI_CR2_FRXTH| SPI_CR2_DS_2|SPI_CR2_DS_1|SPI_CR2_DS_0; SPI1->CR1 |= SPI_CR1_SPE; } @@ -63,12 +70,12 @@ int spi_transfer(const uint8_t *tx, uint8_t *rx, int len){ if (--timeout == 0) return -1; // error by timeout: TX isn't ready } uint8_t out = (tx) ? tx[i] : 0; - *(uint8_t*)&SPI1->DR = out; // ÚÁÐÉÓØ × DR + *((volatile uint8_t*)&SPI1->DR) = out; timeout = 1000000; while(!(SPI1->SR & SPI_SR_RXNE)){ if(--timeout == 0) return 0; } - uint8_t in = *(uint8_t*)&SPI1->DR; // ÞÔÅÎÉÅ ÉÚ DR + uint8_t in = *((volatile uint8_t*)&SPI1->DR); if(rx) rx[i] = in; } //while(SPI1->SR & SPI_SR_BSY){ } diff --git a/F0:F030,F042,F072/usbcan_gpio/spi.h b/F0:F030,F042,F072/usbcan_gpio/spi.h index 50bbb93..81e9dad 100644 --- a/F0:F030,F042,F072/usbcan_gpio/spi.h +++ b/F0:F030,F042,F072/usbcan_gpio/spi.h @@ -20,6 +20,6 @@ #include -void spi_setup(uint32_t speed); // speed in Hz +void spi_setup(); void spi_stop(); int spi_transfer(const uint8_t *tx, uint8_t *rx, int len); diff --git a/F0:F030,F042,F072/usbcan_gpio/usbcangpio.bin b/F0:F030,F042,F072/usbcan_gpio/usbcangpio.bin index 21b1057..172a78e 100755 Binary files a/F0:F030,F042,F072/usbcan_gpio/usbcangpio.bin and b/F0:F030,F042,F072/usbcan_gpio/usbcangpio.bin differ diff --git a/F0:F030,F042,F072/usbcan_gpio/usbcangpio.creator.user b/F0:F030,F042,F072/usbcan_gpio/usbcangpio.creator.user index 69793c4..1ffe426 100644 --- a/F0:F030,F042,F072/usbcan_gpio/usbcangpio.creator.user +++ b/F0:F030,F042,F072/usbcan_gpio/usbcangpio.creator.user @@ -1,6 +1,6 @@ - + EnvironmentId diff --git a/F0:F030,F042,F072/usbcan_gpio/usbcangpio.files b/F0:F030,F042,F072/usbcan_gpio/usbcangpio.files index 73f5be2..40808f5 100644 --- a/F0:F030,F042,F072/usbcan_gpio/usbcangpio.files +++ b/F0:F030,F042,F072/usbcan_gpio/usbcangpio.files @@ -1,3 +1,4 @@ +Readme.md adc.c adc.h can.c diff --git a/F0:F030,F042,F072/usbcan_gpio/version.inc b/F0:F030,F042,F072/usbcan_gpio/version.inc index 6226929..a6d9cf3 100644 --- a/F0:F030,F042,F072/usbcan_gpio/version.inc +++ b/F0:F030,F042,F072/usbcan_gpio/version.inc @@ -1,2 +1,2 @@ -#define BUILD_NUMBER "221" -#define BUILD_DATE "2026-03-17" +#define BUILD_NUMBER "226" +#define BUILD_DATE "2026-03-18"