SPI works, may say it's over

This commit is contained in:
Edward Emelianov
2026-03-18 23:49:29 +03:00
parent c80f0884f5
commit e2a20a046a
12 changed files with 515 additions and 52 deletions

View File

@@ -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 textbased protocol used to communicate with the CAN interface of the USBCANGPIO 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:
```
<timestamp_ms> #<ID> <data0> <data1> ... <dataN-1>
```
- **`<timestamp_ms>`** system time in milliseconds since start.
- **`<ID>`** 11bit CAN identifier in hexadecimal (e.g. `0x123`).
- **`<dataX>`** 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 (101000). Without argument, display the current speed. | `b 250` |
| `I` | Reinitialise 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 humanreadable 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 4byte counter (increasing by 1 each time) using the ID from the last `F` command. | `i` |
| `t <ms>` | 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 (027). `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 <ID>` | 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 busoff 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 poweroff.
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 casesensitive for the singleletter 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"
```

View File

@@ -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;
}

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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 <i2c_speed_t> (wU32);
if(wU32 >= I2C_SPEED_AMOUNT) return ERR_BADVAL;
i2c_speed_t s = static_cast <i2c_speed_t> (wU32);
the_conf.I2Cspeed = static_cast <uint8_t> (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 <int> (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){

View File

@@ -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

View File

@@ -18,6 +18,7 @@
#include <stm32f0.h>
#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){ }

View File

@@ -20,6 +20,6 @@
#include <stdint.h>
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);

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 18.0.2, 2026-03-17T23:56:25. -->
<!-- Written by QtCreator 18.0.2, 2026-03-18T23:46:41. -->
<qtcreator>
<data>
<variable>EnvironmentId</variable>

View File

@@ -1,3 +1,4 @@
Readme.md
adc.c
adc.h
can.c

View File

@@ -1,2 +1,2 @@
#define BUILD_NUMBER "221"
#define BUILD_DATE "2026-03-17"
#define BUILD_NUMBER "226"
#define BUILD_DATE "2026-03-18"