Compare commits

...

8 Commits

Author SHA1 Message Date
Edward Emelianov
45559545ef added russian manual for USB-GPIO 2026-03-19 23:52:09 +03:00
Edward Emelianov
e2a20a046a SPI works, may say it's over 2026-03-18 23:49:29 +03:00
Edward Emelianov
c80f0884f5 add SPI, not working yet (also need to set SPOL/SPHA etc) 2026-03-17 23:57:08 +03:00
Edward Emelianov
165780bef9 I2C works 2026-03-17 21:17:45 +03:00
Edward Emelianov
03b05051aa PWM works 2026-03-15 23:44:58 +03:00
Edward Emelianov
61262435a8 start adding PWM 2026-03-15 02:24:58 +03:00
Edward Emelianov
27eb723d80 add "hex+text" input 2026-03-14 23:44:54 +03:00
Edward Emelianov
57a44f1c66 OK, USART works, maybe add HEX input? 2026-03-14 22:46:28 +03:00
24 changed files with 2042 additions and 200 deletions

View File

@@ -1,5 +1,4 @@
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).
@@ -7,14 +6,401 @@ Unlike original (only USB-CAN) adapter, in spite of it have GPIO contacts on boa
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.
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

@@ -0,0 +1,236 @@
Устройство основано на <a href=https://github.com/eddyem/stm32samples/tree/master/F0%3AF030%2CF042%2CF072/usbcan_ringbuffer>USB-CAN</a>
переходнике.
Функционал платы расширен: от части контактов МК выведены площадки, к которым возможно подпаять проводники, позволяющие задействовать функции GPIO (вход, выход push-pull или opendrain; с подтяжками или без), АЦП, I2C, USART и SPI.
При подключении устройство создает два интерфейса. При использовании соответствующего udev-скрипта (<a href="#udev">см. в конце</a>) автоматически возникают ссылки, по умолчанию: <tt>/dev/USB-CANx</tt> для "старого" интерфейса CAU-USB и <tt>/dev/USB-GPIOx</tt> для "нового".
Устройство эмулирует два CDC-ACM, имея простейший текстовый протокол.
<hr>
<h2>Общий формат команд</h2>
<ul>
<li>Команды чувствительны к регистру, команды-геттеры и команды-сеттеры отличаются наличием в последних знака "=".</li>
<li>Параметры сеттера разделяются пробелом или запятой.</li>
<li>Если не оговорено иное, числовые значения параметров могут иметь десятичный, шестнадцатеричный (<tt>0x...</tt>), восьмеричный (<tt>0x...</tt>) или двоичный (<tt>b...</tt>) формат.</li>
<li>В таких командах, как <tt>SPI</tt> или <tt>I2C</tt> все данные в шестнадцатеричном формате без префикса.</li>
<li>Ввод <b>всегда</b> построчный: символ '\n' означает конец строки и парсер, встретив этот символ, начинает разбор предыдущих символов.</li>
</ul>
<h2>Список команд</h2>
<dl>
<dt><tt>canspeed [=value]</tt></dt><dd>установить скорость CAN-интерфейса по умолчанию (при запуске CAN будет работать на этой скорости). В процессе работы с CAN-USB ее можно временно изменить в соответствии с протоколом (описан на страничке оригинального устройства).</dd>
<dt><tt>curcanspeed</tt></dt><dd>Отобразить текущую скорость CAN.</dd>
<dt><tt>curpinconf</tt></dt><dd>Текущая конфигурация выводов МК (может отличаться от сохраненной в конфигурации в случае ошибки при запуске команды <tt>reinit</tt>.</dd>
<dt><tt>dumpconf</tt></dt><dd>Отображение текущей глобальной конфигурации (в т.ч. параметров CAN, SPI и т.п.). Внимание! Эта конфигурация содержит обновленные данные, которые вы могли изменять в рантайме, поэтому она может не соответствовать актуальной. Для сохранения во флеш-память используйте <tt>saveconf</tt>.</dd>
<dt><tt>eraseflash</tt></dt><dd>Полная очистка области хранения конфигурации во флеш-памяти.</dd>
<dt><tt>help</tt></dt><dd>Отображение справки.</dd>
<dt><tt>hexinput [=0/1]</tt></dt><dd>Изменение режима ввода:
<ul>
<li>0 — простой текст (данные вводятся и выводятся построчно);</li>
<li>1 — шестнадцатеричный ввод (без префикса <tt>0x</tt>) с возможностью ввода текста в кавычках.</li>
</ul>Данный режим влияет лишь на команды <tt>USART</tt> и <tt>sendcan</tt>.</dd>
<dt><tt>iic=addr data...</tt></dt><dd>Запись данных в I2C. Адрес <tt>addr</tt> — несмещенный 7-битный адрес устрйства, данные в шестнадцатеричном формате без разделителя, например: <tt>iic=50 01 02</tt></dd>
<dt><tt>iicread=addr nbytes</tt></dt><dd>Чтение <tt>nbytes</tt> данных по I2C. Данные выводятся в режиме "hexdump".</dd>
<dt><tt>iicreadreg=addr reg nbytes</tt></dt><dd>Считать <tt>nbytes</tt> данных с регистра <tt>reg</tt>.</dd>
<dt><tt>iicscan</tt></dt><dd>Сканирование шины I2C в поиске активных устройств. Найденные рабочие устройства отображаются асинхронно.</dd>
<dt><tt>mcutemp</tt></dt><dd>Условная температура МК, ℃ с множителем 10.</dd>
<dt><tt>mcureset</tt></dt><dd>Перезагрузка микроконтроллера (будьте внимательны: это отразится и на интерфейсе CAN-USB).</dd>
<dt><tt>PAx [= ...]</tt> и <tt>PBx [= ...]</tt></dt><dd>Геттеры и сеттеры для выводов портов GPIOA и GPIOB соответственно (см. раздел «<a href="#pinconfig">Конфигурация выводов</a>»).</dd>
<dt><tt>pinout [=function1,function2,...]</tt></dt><dd>Отображение всех настраиваемых выводов. В случае сеттера отобразятся лишь соответствующие функциональные группы (например, <tt>pinout=ADC, PWM</tt> отобразит выводы, доступные для конфигурирования как входы АЦП или выходы ШИМ). Если вы видите одинаковые функции (например, <tt>PWM2_2</tt> на разных выводах, это — потенциальные конфликты: использовать данный функционал можно лишь на одном из этих выводов.</dd>
<dt><tt>pwmmap</tt></dt><dd>вариант команды <tt>pinout</tt> только для выводов с поддержкой ШИМ, конфликты отмечены явно.</dd>
<dt><tt>readconf</tt></dt><dd>Перечитать последнюю сохраненную в флеш-памяти конфигурацию (полезно, если вы что-то испортили в текущей, но не успели ее сохранить, и хотите "откатиться"). Если устройство "девственно", т.е. ни одной конфигурации не сохранено, это не сработает.</dd>
<dt><tt>reinit</tt></dt><dd>Проверка текущей конфигурации выводов и полная переинициализация. В случае ошибок настройки проблемных выводов будут сброшены в <tt>FL IN</tt>, что можно будет увидеть по вызову <tt>curpinconf</tt>.</dd>
<dt><tt>saveconf</tt></dt><dd>Сохранить текущие настройки во флеш-память.</dd>
<dt><tt>sendcan</tt></dt><dd>Отправить в <b>USB-интерфейс</b> устройства CAN-USB данные. Внимание! Эта команда не пересылает данные в CAN-шину (используйте для этого непосредственно соответствующий интерфейс), но позволяет передать какую-либо информацию процессу, работающую с этим интерфейсом.</dd>
<dt><tt>setiface=N [=name]</tt></dt><dd>Команда позволяет переименовать интерфейсы (0 = CAN, 1 = GPIO). Используйте удобные для вас наименования, не превышающие 16 символов. Помните, что udev-скрипт будет создавать ссылки с соответствующими именами, поэтому не используйте недопустимых символов (пробелы, слэши и прочее, что не допустимо в именах файлов).</dd>
<dt><tt>SPI=data...</tt> или <tt>SPI=size</tt></dt><dd>Обмен данными с устройством SPI. Первая команда работает в полнодуплексном режиме или режиме TX-only, передавая все введенные в шестнадцатеричном формате данные. В случае полнодуплексного режима считывается такое же количество данных с MISO и выводится как "hexdump". В режиме Rx-only используется второй вариант команды, считывая <tt>size</tt> байт с SPI.</dd>
<dt><tt>time</tt></dt><dd>Отображение условного времени (в мс), прошедшего с последней перезагрузки МК.</dd>
<dt><tt>USART[=...]</tt></dt><dd>Геттер считывает данные из приемного кольцевого буфера USART, сеттер отправляет перечисленные данные (их формат зависит от выбранного командой <tt>hexinput</tt> режима).</dd>
<dt><tt>vdd</tt></dt><dd>Примерное напряжение питания МК (В с множителем 100).</dd>
</dl>
<hr>
<h2 id="pinconfig">Конфигурация выводов (команды <tt>PAx</tt>/<tt>PBx</tt>)</h2>
Общий синтаксис настройки выводов следующий:
<pre>PXx = MODE PULL OTYPE FUNC MISC ...</pre>
Здесь <tt>PX</tt> — соответствующий порт (<tt>PA</tt> или <tt>PB</tt>), <tt>x</tt> — номер вывода. После знака равенства идет последовательность ключевых слов с возможными числовыми аргументами.
Кроме того, эта же команда работает как сеттер/геттер значения, если соответствующий вывод настроен как GPIO, АЦП (только геттер) или ШИМ. Например, если PA1 настроен как <tt>OUT</tt>, команда <tt>PA1=1</tt> установит его значение в высокое состояние, геттер же отобразит текущее состояние (из регистра <tt>GPIOX->IDR</tt>, т.е. для выхода с открытым стоком в состоянии "1" можно детектировать внешнюю верхнюю или нижнюю подтяжку).
Для настройки ШИМ (<tt>PWM</tt>) можно изменять заполнение выходного сигнала, например: <tt>PB2=128</tt> установит заполнение в 50%.
<h3>Ключевые слова</h3>
Ключевые слова могут идти в любой последовательности.
<h4>MODE</h4>
<dl>
<dt><tt>AIN</tt></dt><dd>Аналоговый вход (АЦП).</dd>
<dt><tt>IN</tt></dt><dd>Цифровой вход.</dd>
<dt><tt>OUT</tt></dt><dd>Цифровой выход.</dd>
<dt><tt>AF</tt></dt><dd>Альтернативная функция (данное ключевое слово не обязательно указывать, т.к. этот флаг установится автоматически при выборе любой функции вроде <tt>USART</tt> и т.п.).</dd>
</dl>
<h4>PULL</h4>
(только для режимов <tt>GPIO</tt>)
<dl>
<dt><tt>PU</tt></dt><dd>Включена верхняя подтяжка (как для входов, так и для выходов).</dd>
<dt><tt>PD</tt></dt><dd>Включена нижняя подтяжка (-//-, хоть для выходов это бессмысленно).</dd>
<dt><tt>FL</tt></dt><dd>Подтяжки выключены.</dd>
</dl>
Верхняя и нижняя подтяжки взаимоисключающие.
<h4>OTYPE</h4>
(только для режимов <tt>GPIO</tt>)
Настройка типа выхода (для входов не актуальна):
<dl>
<dt><tt>PP</tt></dt><dd>Push-pull.</dd>
<dt><tt>OD</tt></dt><dd>Открытый сток.</dd>
</dl>
<h4>FUNC</h4>
Выбор альтернативной функции:
<dl>
<dt><tt>USART</tt></dt><dd>Вывод будет частью USART (можно выбрать только Rx или Tx).</dd>
<dt><tt>SPI</tt></dt><dd>Вывод — часть SPI (обязательно выбрать SCK и хотя бы один из MISO или MOSI).</dd>
<dt><tt>I2C</tt></dt><dd>Вывод — часть I2C (обязательно выбрать как SCL, так и SDA).</dd>
<dt><tt>PWM</tt></dt><dd>Вывод с функцией ШИМ (частота ШИМ около 23.5кГц, заполнение от 0 до 255).</dd>
</dl>
<h4>MISC</h4>
<dl>
<dt><tt>MONITOR</tt></dt><dd>Асинхронный мониторинг функционала данного вывода: без запроса пользователя передаются данные, как только значение GPIO входа или АЦП изменится, либо же в случае USART — появятся новые данные.</dd>
<dt><tt>THRESHOLD</tt></dt><dd>Пороговое значение (0..4095) для мониторинга аналогового входа. Изменение асинхронно отображается лишь при превышении текущим значением данного порога относительно предыдущего.</dd>
<dt><tt>SPEED</tt></dt><dd>Установка скорости интерфейса (USART, SPI, I2C), в бодах, Гц или индексе.</dd>
<dt><tt>TEXT</tt></dt><dd>(только USART) Текстовый режим USART: входные и выходные данные буферизуются построчно. В случае включенного мониторинга отображаются лишь новые строки целиком, но не их части (в случае, если длина строки не превышает размера входного буфера).</dd>
<dt><tt>HEX</tt></dt><dd>(только USART) Шестнадцатеричный режим USART: блоки входных данных (не превышающих размер внутреннего буфера) разделяются по сигналу IDLE. Вывод — в формате "hexdump".</dd>
<dt><tt>LSBFIRST</tt></dt><dd>(только SPI) Передача младшего бита первым. По умолчанию — MSBFIRST.</dd>
<dt><tt>CPOL</tt></dt><dd>(только SPI) полярность данных.</dd>
<dt><tt>CPHA</tt></dt><dd>(только SPI) выбор фронта активности данных.</dd>
</dl>
При выборе функционала, недоступного на данном выводе, сразу же будет выведена ошибка. В случае, если вы неправильно настроите выводы интерфейсов (скажем, забудете указать SCK для SPI), после вызова команды <tt>reinit</tt> появится сообщение об ошибке настройки, а проблемные места будут сброшены в значения по умолчанию.
<hr>
<h2>Альтернативные функции</h2>
<h3>USART</h3>
Данный интерфейс доступен на контактах PA9/PB6 (TX USART1), PA10/PB7 (RX USART1) или PA2 (TX USART2) и PA2 (RX USART2). Несмотря на наличие двух модулей USART, допускается использовать лишь один из них. Соответственно, нельзя настроить смесь для разных интерфейсов (скажем, PA9 и PA3). Прием и передача данных использует DMA, поэтому большие объемы данных, передаваемые по этому интерфейсу, не будут сказываться на работе преобразователя CAN-USB.
Для данного интерфейса при конфигурации используются такие ключевые слова, как SPEED, TEXT/HEX и MONITOR. Например,
<pre>PA9 = USART SPEED 115200 TEXT MONITOR
PA10 = USART
reinit</pre>
Данная последовательность команд настроит USART1 на выводах PA9/PA10. Скорость 115200 бод, текстовый режим с асинхронным выводом поступающих строк текста. Каждая новая строка будет выведена как <tt>USART = ...</tt>.
Обратите внимание: для отличия реального символа конца строки от переноса строки в случае вывода неполного буфера (например, при переполнении входного буфера), "реальный" символ '\n' удваивается.
Передача строк зависит от формата, например, в формате TEXT команда <tt>USART=Hello, world!\n</tt> отправит по USART строку "Hello, world!\n". А в формате HEX команда <tt>USART=48 65 6c 6c 6f\n</tt> отправит пять байт данных "Hello".
Команда-геттер <tt>USART</tt> отображает содержимое приемного кольцевого буфера. В "текстовом" режиме оно будет выводиться построчно на каждый запрос. В "шестнадцатеричном" режиме — порциями по 256 байт. Если кольцевой буфер пуст, никакого ответа на эту команду не последует. Даже вне режима MONITOR возможен асинхронный вывод данных в случае переполнения буфера: вызывайте геттер чаще, чтобы этого не допустить.
Длина входного буфера DMA ограничена, поэтому если пользователь настроит USART на слишком высокую скорость, возможна потеря данных (особенно при активной работе второго интерфейса — CAN-USB), т.к. опрос буфера DMA производится без прерываний.
<h3>I2C</h3>
Вы можете выбрать интерфейс на контактах PB6/PB10 (SCL) и PB7/PB11 (SDA). Будьте внимательны: если вместо STM32F042 используется STM32F072, на контактах PB7/PB11 доступен лишь I2C2, но не I2C1. В этом случае настроить I2C на данных контактах будет невозможным (в коде нет поддержки I2C2).
I2C не допускает активации только с одним из этих выводов, поэтому обязательно настроить и SCL, и SDA.
Для данного интерфейса доступно лишь ключевое слово SPEED. Обратите внимание, что, в отличие от USART и SPI, здесь скорость задается <b>индексом</b>: 0=10kHz, 1=100kHz, 2=400kHz, 3=1MHz.
Пример:
<pre>PB6 = I2C SPEED 2
PB7 = I2C
reinit</pre>
Активирует I2C на PB6/PB7 со скоростью 400кГц.
Прием и передача данных производится при помощи команд <tt>iic</tt>, <tt>iicread</tt>, <tt>iicreadreg</tt>. Командой <tt>iicscan</tt> можно произвести поиск активных устройств на шине.
Все данные вводятся и выводятся в двоичном формате без префикса, в том числе адрес и длина данных.
<h3>SPI</h3>
Доступен SPI на выводах PA5/PB3 (SCK), PA6/PB4 (MISO) и PA7/PB5 (MOSI).
Не обязательно настраивать все три вывода, если вам нужна односторонняя передача данных.
Ключевые слова:
<ul>
<li><tt>SPEED</tt> — скорость, Гц, при настройке будет подобран актуальный делитель системной тактовой частоты, наиболее близкий к заданной скорости (не ожидайте получить скорость 30МГц при тактовой 48МГц),</li>
<li><tt>CPOL</tt> — полярность SCK (1 — высокий в неактивном состоянии),</li>
<li><tt>CPHA</tt> — фронт SCK на актуальный уровень сигнала (1 — второй фронт),</li>
<li><tt>LSBFIRST</tt> — передача первым младшего бита.</li>
</ul>
По умолчанию флаги CPOL/CPHA/LSBFIRST считаются равными нулю.
Пример, последовательность команд:
<pre>PA5 = SPI SPEED 2000000 CPOL CPHA
PA6 = SPI
PA7 = SPI
reinit</pre>
настроит SPI на контактах PA5/PA6/PA7, скорость 2МГц, режим 3 (CPOL=CPHA=1), полный дуплекс.
В режиме полного дуплекса сеттер <tt>SPI=...</tt> передает перечисленные после знака "равно" данные (в шестнадцатеричном формате без разделителя) и принимает такое же количество данных. Принятые данные будут выведены после завершения операции в виде <tt>SPI = ...</tt>.
В RX-only режиме (т.е. когда не сконфигурирован вывод MOSI), формат геттера другой: <tt>SPI = datalen</tt>, где указывается количеств байт для считывания (<b>в любом формате</b> числа!). Скажем, <tt>SPI = 0x10</tt> считает 16 байт данных, равно как и <tt>SPI=16</tt>, <tt>SPI = 020</tt> или <tt>SPI=b10000</tt>.
<h3>ШИМ (PWM)</h3>
Доступные для работы в режиме ШИМ выводы можно отобразить запросом <tt>pwmmap</tt> или <tt>pinout=PWM</tt>. При настройке новых режимов командой <tt>reinit</tt> будет проведен анализ конфликтов, поэтому в случае выбора ШИМ на конфликтующих контактах (т.е. если один и тот же канал одного и того же таймера выбран в качестве ШИМ-выхода на разных кнтактах) будет отображено сообщение об ошибке со сбросом конфликтов в значение по умолчанию.
Для конфигурации никаких дополнительных параметров указывать не нужно, лишь <tt>PXx = PWM</tt>. Сеттер позволяет установить требуемое заполнение (0..255), а геттер указывает текущее.
Например,
<pre>PA1 = PWM
reinit
PA1=128</pre>
установит PA1 в ШИМ-режим с заполнением 50%. Частота ШИМ фиксирована: около 23.5кГц и настройке не подлежит.
<hr>
<h2>Асинхронный мониторинг</h2>
При указании флага <tt>MONITOR</tt> в настройке контакта в случае, если выбранная функция это поддерживает, по USB будут выдаваться сообщения с новыми данными на этом контакте вне зависимости от запроса пользователя.
Для входа или выхода с открытым стоком изменение значения будет отображаться, как, например <tt>PB3 = 1</tt> при смене низкого уровня сигнала на высокий.
В случае входа АЦП дополнительным параметром <tt>THRESHOLD</tt> задается пороговое значение: если уровень сигнала на входе отличается от предыдущего больше, чем на это пороговое значение, будет выведено соответствующее сообщение, например <tt>PA1 = 3456</tt>.
Также "мониторинг" доступен для асинхронного отображения входящих по USART сообщений (в текстовом режиме данные выводятся построчно, в "hex" — с буферизацией по блокам, разделенным IDLE). Если режим мониторинга отключен, то пользователь должен регулярными вызовами геттера <tt>USART</tt> считывать пришедшие данные из кольцевого буфера. В случае переполнения буфера он будет автоматически очищаться, а имеющаяся в нем информация — асинхронно передаваться пользователю.
Интерфейсы I2C и SPI работают исключительно с блокирующими операциями, поэтому флаг мониторинга к ним не применим.
<hr>
<h2>Сохранение настроек</h2>
Если вы окончательно настроили все интерфейсы и контакты, и хотите, чтобы при следующем включении устройство был сконфигурировано точно так же, сохраните настройки во флеш-память при помощи команды <tt>saveconf</tt>. Если произойдет ошибка сохранения, попробуйте стереть весь отведенный под хранение настроек блок данных при помощи <tt>eraseflash</tt>, а затем сохранить снова.
При включении устройства МК автоматически загружает последние сохраненные настройки, пользуясь бинарным поиском (что позволяет не тратить слишком много времени на перебор всего объема доступного хранилища, хотя, конечно, для STM32F042 это неактуально).
Настройки во флеш-памяти хранятся последовательно. При достижении последней записи в отведенном регионе (все незанятые блоки флеш-памяти после микрокода устройства), стирается все хранилище, а затем новая запись пишется первой. В такие моменты будьте осторожными, т.к. сбой питания может повредить настройки, и их придется выставлять заново.
<hr>
<h2>Внутренние коды ошибок</h2>
Часть сеттеров, не возвращающих в свою очередь геттеры, выдает следующие коды ошибок:
<dl>
<dt><tt>OK</tt></dt><dd>Выполнение команды прошло успешно.</dd>
<dt><tt>BADCMD</tt></dt><dd>Введена неправильная команда.</dd>
<dt><tt>BADPAR</tt></dt><dd>Неправильный параметр команды (например, номер контакта вне диапазона 0..15).</dd>
<dt><tt>BADVAL</tt></dt><dd>Неправильное значение сеттера (например, вы ошиблись в написании ключевого слова или ввели неверную скорость и т.п.).</dd>
<dt><tt>WRONGLEN</tt></dt><dd>Неправильная длина сообщения (слишком большое или нулевой длины).</dd>
<dt><tt>CANTRUN</tt></dt><dd>Ошибка выполнения команды (например, при отсутствии на шине I2C устройства с идентификатором, в который пытаются отправить данные).</dd>
<dt><tt>BUSY</tt></dt><dd>Попробуйте вызвать команду позже (актуально лишь для USART: если вы попытаетесь послать следующее сообщение, пока еще не передано предыдущее).</dd>
<dt><tt>OVERFLOW</tt></dt><dd>Переполнение длины входного буфера.</dd>
</dl>
Некоторые команды не возвращают вообще ничего (например, геттер <tt>USART</tt>, если в буфере ничего нет). По запросу геттеров выводится имя команды и данные после знака "равно".
<hr>
<hr>
<h2 id="udev">Как различать несколько одинаковых подключенных устройств</h2>
Данное устройство представляет собой "составное устройство USB", состоящее из двух CDC-ACM интерфейсов. Поэтому по умолчанию при его включении в <tt>/dev</tt> вы увидите появление устройств <tt>/dev/ttyACMx</tt> и <tt>/dev/ttyACM(x+1)</tt> (где x зависит от количества уже подключенных устройств). Чтобы различать интерфейсы (особенно если вы включили несколько одинаковых таких преобразователей CAN-USB-GPIO), в USB-дескрипторах сохраняются символьные имена для соответствующих полей <tt>iInterface</tt>. Изменить их вы можете при помощи команды <tt>setiface</tt>.
В терминале при помощи <tt>lsusb -v</tt> можно увидеть эти поля.
Т.к. при подключении USB-устройств udev может "перехватить" их и выполнить какие-то действия, приведенный скрипт поможет создавать человекочитаемые симлинки.
Например, если вы задаете интерфейсам названия «myCAN» и «myGPIO» и сохраняете конфигурацию во флеш-памяти МК, после переподключения устройства вы увидите симлинки: <tt>/dev/ttymyCAN0</tt> и <tt>/dev/ttymyGPIO0</tt>, что позволит быстро их отождествить (особенно это актуально для служб, запускаемых при старте системы).
Просто добавьте этот файл в <tt>/etc/udev/rules.d</tt>:
<code lang="C"><pre style="border-left: 4px solid; border-top: 1px dashed; border-bottom: 1px dashed; max-height: 300px; overflow: auto; padding: 5px" title="Code block">
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"
</pre></code>

View File

@@ -39,17 +39,21 @@ static int write2flash(const void*, const void*, uint32_t);
// 'memcpy' forming offset 8 is out of the bounds [0, 4] of object '__varsstart' with type 'uint32_t'
const user_conf *Flash_Data = (const user_conf *)(&__varsstart);
// default pin config: all are low speed floating inputs
// default pin config
// simple FL IN
#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, \
[6] = PINEN, [7] = PINEN, [9] = PINEN, [10] = PINEN
[6] = PINEN, [7] = PINEN, [9] = U1, [10] = U1
// 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),
@@ -60,6 +64,8 @@ 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
@@ -192,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,18 +38,29 @@
// 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!!!)
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
pinconfig_t pinconfig[2][16]; // GPIOA, GPIOB
usartconf_t usartconfig;
//spiconfig_t spiconfig;
uint8_t I2Cspeed; // I2C speed index
} user_conf;
extern user_conf the_conf; // global user config (read from FLASH to RAM)
@@ -61,3 +72,4 @@ void flashstorage_init();
int store_userconf();
void dump_userconf();
int erase_storage();
uint32_t storage_capacity();

View File

@@ -22,6 +22,9 @@
#include "adc.h"
#include "flash.h"
#include "gpio.h"
#include "i2c.h"
#include "pwm.h"
#include "spi.h"
#include "usart.h"
static uint16_t monitor_mask[2] = {0}; // pins to monitor == 1 (ONLY GPIO and ADC)
@@ -38,57 +41,65 @@ typedef struct{
uint8_t AF[FUNC_AMOUNT]; // alternate function number for corresponding `FuncNames` number
} pinprops_t;
#define CANADC(x) ((x) & (1<<FUNC_AIN))
#define CANUSART(x) ((x) & (1<<FUNC_USART))
#define CANSPI(x) ((x) & (1<<FUNC_SPI))
#define CANI2C(x) ((x) & (1<<FUNC_I2C))
// AF for USART, SPI, I2C:
#define _U(x) [FUNC_USART] = x
// _S(0) or _U(0) have no sence, but lets understand that this pin have SPI or USART
#define _S(x) [FUNC_SPI] = x
#define _I(x) [FUNC_I2C] = x
#define _P(x) [FUNC_PWM] = x
// Here included only common AF for STM32F042 and STM32F072 and without negative timer outputs (stars - collisions)
static const pinprops_t pin_props[2][16] = {
[0] = { // PORT A
[0] = { .funcs = 0b00000001, .AF = {0}}, // PA0: ADC0, AF2 (TIM2_CH1)
[1] = { .funcs = 0b00000001, .AF = {0}}, // PA1: ADC1, AF2 (TIM2_CH2)
[2] = { .funcs = 0b00000011, .AF = {_U(1)}}, // PA2: ADC2, AF2 (TIM2_CH3), AF1 (USART2_TX)
[3] = { .funcs = 0b00000011, .AF = {_U(1)}}, // PA3: ADC3, AF2 (TIM2_CH4), AF1 (USART2_RX)
[0] = { .funcs = 0b00000001, .AF = {0}}, // PA0: ADC0
[1] = { .funcs = 0b00010001, .AF = {_P(2)}}, // PA1: ADC1, AF2 (TIM2_CH2*)
[2] = { .funcs = 0b00010011, .AF = {_U(1), _P(2)}}, // PA2: ADC2, AF2 (TIM2_CH3**), AF1 (USART2_TX)
[3] = { .funcs = 0b00010011, .AF = {_U(1), _P(2)}}, // PA3: ADC3, AF2 (TIM2_CH4***), AF1 (USART2_RX)
[5] = { .funcs = 0b00000101, .AF = {_S(0)}}, // PA5: ADC5, AF9 (SPI1_SCK)
[6] = { .funcs = 0b00000101, .AF = {_S(0)}}, // PA6: ADC6, AF0 (SPI1_MISO)
[7] = { .funcs = 0b00000101, .AF = {_S(0)}}, // PA7: ADC7, AF0 (SPI1_MOSI)
[9] = { .funcs = 0b00000010, .AF = {_U(1)}}, // PA9: AF1 (USART1_TX)
[10] = { .funcs = 0b00000010, .AF = {_U(1)}}, // PA10: AF1 (USART1_RX)
[6] = { .funcs = 0b00010101, .AF = {_S(0), _P(5)}}, // PA6: ADC6, AF0 (SPI1_MISO), AF5 (TIM16_CH1)
[7] = { .funcs = 0b00010101, .AF = {_S(0), _P(4)}}, // PA7: ADC7, AF0 (SPI1_MOSI), AF4 (TIM14_CH1)
[9] = { .funcs = 0b00010010, .AF = {_U(1), _P(2)}}, // PA9: AF1 (USART1_TX), AF2 (TIM1_CH2)
[10] = { .funcs = 0b00010010, .AF = {_U(1), _P(2)}}, // PA10: AF1 (USART1_RX), AF2 (TIM1_CH3)
},
[1] = { // PORT B
[0] = { .funcs = 0b00000001, .AF = {0}}, // PB0: ADC8, AF1 (TIM3_CH3), AF2 (TIM1_CH2N)
[1] = { .funcs = 0b00000001, .AF = {0}}, // PB1: ADC9, AF0 (TIM14_CH1), AF1 (TIM3_CH4), AF2 (TIM1_CH3N)
[0] = { .funcs = 0b00010001, .AF = {_P(1)}}, // PB0: ADC8, AF1 (TIM3_CH3)
[1] = { .funcs = 0b00010001, .AF = {_P(1)}}, // PB1: ADC9, AF1 (TIM3_CH4)
[2] = { .funcs = 0b00000000, .AF = {0}}, // PB2: nothing except GPIO
[3] = { .funcs = 0b00000100, .AF = {_S(0)}}, // PB3: AF0, (SPI1_SCK), AF2 (TIM2_CH2)
[4] = { .funcs = 0b00000100, .AF = {_S(0)}}, // PB4: AF0 (SPI1_MISO), AF1 (TIM3_CH1)
[5] = { .funcs = 0b00000100, .AF = {_S(0)}}, // PB5: AF0 (SPI1_MOSI), AF1 (TIM3_CH2)
[6] = { .funcs = 0b00001010, .AF = {_U(0), _I(1)}}, // PB6: AF0 (USART1_TX), AF1 (I2C1_SCL), AF2 (TIM16_CH1N)
[7] = { .funcs = 0b00001010, .AF = {_U(0), _I(1)}}, // PB7: AF0 (USART1_RX), AF1 (I2C1_SDA), AF2 (TIM17_CH1N)
[10] = { .funcs = 0b00001000, .AF = {_I(1)}}, // PB10: AF1 (I2C1_SCL), AF2 (TIM2_CH3)
[11] = { .funcs = 0b00001000, .AF = {_I(1)}}, // PB11: AF1 (I2C1_SDA), AF2 (TIM2_CH4)
[3] = { .funcs = 0b00010100, .AF = {_S(0), _P(2)}}, // PB3: AF0, (SPI1_SCK), AF2 (TIM2_CH2*)
[4] = { .funcs = 0b00010100, .AF = {_S(0), _P(1)}}, // PB4: AF0 (SPI1_MISO), AF1 (TIM3_CH1)
[5] = { .funcs = 0b00010100, .AF = {_S(0), _P(1)}}, // PB5: AF0 (SPI1_MOSI), AF1 (TIM3_CH2)
[6] = { .funcs = 0b00001010, .AF = {_U(0), _I(1)}}, // PB6: AF0 (USART1_TX), AF1 (I2C1_SCL)
[7] = { .funcs = 0b00001010, .AF = {_U(0), _I(1)}}, // PB7: AF0 (USART1_RX), AF1 (I2C1_SDA)
[10] = { .funcs = 0b00011000, .AF = {_I(1), _P(2)}}, // PB10: AF1 (I2C1_SCL), AF2 (TIM2_CH3**)
[11] = { .funcs = 0b00011000, .AF = {_I(1), _P(2)}}, // PB11: AF1 (I2C1_SDA), AF2 (TIM2_CH4***)
}
};
#undef _U
#undef _S
#undef _I
#define CANADC(x) ((x) & (1<<FUNC_AIN))
#define CANUSART(x) ((x) & (1<<FUNC_USART))
#define CANSPI(x) ((x) & (1<<FUNC_SPI))
#define CANI2C(x) ((x) & (1<<FUNC_I2C))
#define CANPWM(x) ((x) & (1<<FUNC_PWM))
static uint8_t haveI2C = 0; // ==1 if chkpinconf found I2C
static uint8_t haveSPI = 0;
// return pin_props[port][pin].funcs for listing or -1 if disabled
int pinfuncs(uint8_t port, uint8_t pin){
if(is_disabled(port, pin)) return -1;
return (int) pin_props[port][pin].funcs;
}
typedef struct{
uint8_t isrx : 1;
uint8_t istx : 1;
} usart_props_t;
/**
* @brief get_usart_index - get USART index (0 or 1 for USART1 or USART2) by given pin
* @param port
* @param pin
* @return -1 if error
*/
static int get_usart_index(uint8_t port, uint8_t pin, usart_props_t *p){
int get_usart_index(uint8_t port, uint8_t pin, usart_props_t *p){
if(port > 1 || pin > 15 || !CANUSART(pin_props[port][pin].funcs)) return -1;
int idx = -1;
usart_props_t curprops = {0};
if(port == 0){ // GPIOA
@@ -130,6 +141,78 @@ static int get_usart_index(uint8_t port, uint8_t pin, usart_props_t *p){
return idx;
}
// return -1 if pin can't I2C, or return 0 and fill `p`
int get_i2c_index(uint8_t port, uint8_t pin, i2c_props_t *p){
if(port > 1 || pin > 15 || !CANI2C(pin_props[port][pin].funcs)) return -1;
int idx = -1; // later we can add SPI2 support
i2c_props_t curprops = {0};
if(port == 1){ // only GPIOB
switch(pin){
case 6: // PB6 - I2C1_SCL
idx = 0;
curprops.isscl = 1;
break;
case 7: // PB7 - I2C1_SDA
idx = 0;
curprops.issda = 1;
break;
case 10: // PB10 - I2C1_SCL
idx = 0;
curprops.isscl = 1;
break;
case 11: // PB11 - I2C1_SDA
idx = 0;
curprops.issda = 1;
break;
default:
break;
}
}
if(p) *p = curprops;
return idx;
}
int get_spi_index(uint8_t port, uint8_t pin, spi_props_t *p){
if(port > 1 || pin > 15 || !CANSPI(pin_props[port][pin].funcs)) return -1;
int idx = -1;
spi_props_t curprops = {0};
if(port == 0){ // PA5-7 (SCK-MISO-MOSI)
switch(pin){
case 5:
idx = 0;
curprops.issck =1;
break;
case 6:
idx = 0;
curprops.ismiso = 1;
break;
case 7:
idx = 0;
curprops.ismosi = 1;
break;
default: break;
}
}else if(port == 1){ // PB3-5 (SCK-MISO-MOSI)
switch(pin){
case 3:
idx = 0;
curprops.issck =1;
break;
case 4:
idx = 0;
curprops.ismiso = 1;
break;
case 5:
idx = 0;
curprops.ismosi = 1;
break;
default: break;
}
}
if(p) *p = curprops;
return idx;
}
// default config
static void defconfig(pinconfig_t *cfg){
if(!cfg) return;
@@ -155,6 +238,10 @@ int chkpinconf(){
UC.RXen = 0; UC.TXen = 0; UC.monitor = 0;
}
int active_usart = -1; // number of USART if user selects it (we can't check it by UC->idx)
int active_i2c = -1;
int active_spi = -1;
i2c_props_t i2cprops = {0};
spi_props_t spiprops = {0};
for(int port = 0; port < 2; ++port){
for(int pin = 0; pin < 16; ++pin){
pinconfig_t *cfg = &pinconfig[port][pin];
@@ -192,6 +279,96 @@ int chkpinconf(){
if(cfg->monitor) UC.monitor = 1;
}
break;
case FUNC_PWM:{
pwmtimer_t pwm;
if(!canPWM(port, pin, &pwm)){
DBG("Can't PWM\n");
defconfig(cfg);
ret = FALSE;
break;
}
if(pwm.collision && pinconfig[pwm.collport][pwm.collpin].af == FUNC_PWM){
DBG("Found collision -> remove\n");
defconfig(&pinconfig[pwm.collport][pwm.collpin]); // set later collision to defaults
ret = FALSE;
break;
}
}
break;
case FUNC_I2C:{
i2c_props_t ip;
int i2c_idx = get_i2c_index(port, pin, &ip);
if(i2c_idx < 0){
defconfig(cfg);
ret = FALSE;
break;
}
// maybe for 2 I2Cs
if(active_i2c == -1) active_i2c = i2c_idx;
else if(active_i2c != i2c_idx){
// collision
defconfig(cfg);
ret = FALSE;
break;
}
if(ip.isscl){
if(i2cprops.isscl){ // two SCLs
defconfig(cfg);
ret = FALSE;
break;
}
i2cprops.isscl = 1;
}
if(ip.issda){
if(i2cprops.issda){ // two SDAs
defconfig(cfg);
ret = FALSE;
break;
}
i2cprops.issda = 1;
}
}
break;
case FUNC_SPI:{
spi_props_t sp;
int spi_idx = get_spi_index(port, pin, &sp);
if(spi_idx < 0){
defconfig(cfg);
ret = FALSE;
break;
}
if(active_spi == -1) active_spi = spi_idx;
else if(active_spi != spi_idx){
defconfig(cfg);
ret = FALSE;
break;
}
if(sp.issck){
if(spiprops.issck){
defconfig(cfg);
ret = FALSE;
break;
}
spiprops.issck = 1;
}
if(sp.ismiso){
if(spiprops.ismiso){
defconfig(cfg);
ret = FALSE;
break;
}
spiprops.ismiso = 1;
}
if(sp.ismosi){
if(spiprops.ismosi){
defconfig(cfg);
ret = FALSE;
break;
}
spiprops.ismosi = 1;
}
}
break;
default: break; // later fill other functions
}
}
@@ -200,17 +377,42 @@ int chkpinconf(){
}
// now check USART configuration
if(active_usart != -1){
DBG("Got active USART\n");
UC.idx = active_usart;
if(!chkusartconf(&UC)) ret = FALSE;
}else{
DBG("No active USARTs\n");
get_defusartconf(&UC); // clear global configuration
the_conf.usartconfig = UC;
}
// check active I2C
if(active_i2c != -1){
if(i2cprops.isscl && i2cprops.issda){
haveI2C = 1;
}else{
DBG("Need two pins for I2C\n");
ret = FALSE;
haveI2C = 0;
}
}
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;
haveSPI = 0;
}
}
return ret;
}
int is_disabled(uint8_t port, uint8_t pin){
if(port > 1 || pin > 15) return FALSE;
if(port > 1 || pin > 15) return TRUE;
if(the_conf.pinconfig[port][pin].enable) return FALSE;
return TRUE;
}
@@ -309,7 +511,7 @@ int gpio_reinit(){
gpio->OSPEEDR = (gpio->OSPEEDR & ~(3 << shift2)) | (cfg->speed << shift2);
gpio->PUPDR = (gpio->PUPDR & ~(3 << shift2)) | (cfg->pull << shift2);
if(pin < 8){
int shift4 = pin << 4;
int shift4 = pin << 2;
gpio->AFR[0] = (gpio->AFR[0] & ~(0xf << shift4)) | (cfg->afno << shift4);
}else{
int shift4 = (pin - 8) << 2;
@@ -328,6 +530,18 @@ int gpio_reinit(){
oldstates[port][pin] = (gpio->IDR >> pin) & 1;
}
}
// start/stop PWM on this pin
if(cfg->mode == MODE_AF && cfg->af == FUNC_PWM){
if(!startPWM(port, pin)) ret = FALSE;
}else{ // check for collisions
pwmtimer_t t;
if(canPWM(port, pin, &t)){
if(t.collision){ // stop PWM only if "earlier" channel don't set on this
if((t.collport < port || t.collpin < pin) && (pinconfig[t.collport][t.collpin].af != FUNC_PWM))
stopPWM(port, pin);
}else stopPWM(port, pin);
}
}
}
}
// if all OK, copy to the_conf
@@ -338,7 +552,11 @@ int gpio_reinit(){
if(get_curusartconf(&usc) && (usc.RXen | usc.TXen)){
if(!usart_config(NULL)) ret = FALSE;
else if(!usart_start()) ret = FALSE;
}
}else usart_stop();
if(haveI2C) i2c_setup((i2c_speed_t) the_conf.I2Cspeed);
else i2c_stop();
if(haveSPI) spi_setup();
else spi_stop();
return ret;
}
@@ -382,12 +600,18 @@ int16_t pin_in(uint8_t port, uint8_t pin){
case MODE_OUTPUT:
if(GPIOx->IDR & (1<<pin)) val = 1;
else val = 0;
break;
break;
case MODE_ANALOG:{
int8_t chan = get_adc_channel(port, pin);
if(chan >= 0){
return (int16_t)getADCval(chan); // getADCval ×ÏÚ×ÒÁÝÁÅÔ uint16_t
}
if(chan >= 0)
val = (int16_t) getADCval(chan);
}
break;
case MODE_AF:{
pinconfig_t curconf;
if(!get_curpinconf(port, pin, &curconf)) return -1;
if(curconf.af == FUNC_PWM)
val = getPWM(port, pin);
}
break;
default:
@@ -434,3 +658,4 @@ uint16_t gpio_alert(uint8_t port){
}
return alert;
}

View File

@@ -19,6 +19,7 @@
#pragma once
#include <stdint.h>
#include <stm32f0.h>
#ifdef EBUG
#define USBIF IGPIO
@@ -60,13 +61,15 @@ typedef enum{
} pinspeed_t;
// !!! FuncNames means position of bit in funcvalues_t.flags!
enum FuncNames{ // shift 1 by this to get "canUSART" etc; not more than 7!
typedef enum FuncNames{ // shift 1 by this to get "canUSART" etc; not more than 7!
FUNC_AIN = 0,
FUNC_USART = 1,
FUNC_SPI = 2,
FUNC_I2C = 3,
FUNC_PWM = 4,
FUNC_AMOUNT // just for arrays' sizes
};
} funcnames_t;
/*
typedef union{
struct{
@@ -84,11 +87,27 @@ typedef struct{
pinout_t otype : 1;
pinspeed_t speed : 2;
uint8_t afno : 3; // alternate function number (only if mode == MODE_AF)
uint8_t af : 3; // alternate function name (`FuncNames`)
funcnames_t af : 3; // alternate function name (`FuncNames`)
uint8_t monitor : 1; // monitor changes
uint16_t threshold; // threshold for ADC measurement
} pinconfig_t;
typedef struct{
uint8_t isrx : 1;
uint8_t istx : 1;
} usart_props_t;
typedef struct{
uint8_t isscl : 1;
uint8_t issda : 1;
} i2c_props_t;
typedef struct{
uint8_t issck : 1;
uint8_t ismiso : 1;
uint8_t ismosi : 1;
} spi_props_t;
/*
typedef struct{
uint32_t speed;
@@ -100,11 +119,16 @@ typedef struct{
*/
int is_disabled(uint8_t port, uint8_t pin);
int pinfuncs(uint8_t port, uint8_t pin);
int chkpinconf();
int set_pinfunc(uint8_t port, uint8_t pin, pinconfig_t *pcfg);
int get_curpinconf(uint8_t port, uint8_t pin, pinconfig_t *c);
int get_usart_index(uint8_t port, uint8_t pin, usart_props_t *p);
int get_i2c_index(uint8_t port, uint8_t pin, i2c_props_t *p);
int get_spi_index(uint8_t port, uint8_t pin, spi_props_t *p);
int gpio_reinit();
int pin_out(uint8_t port, uint8_t pin, uint8_t newval);

View File

@@ -27,6 +27,9 @@ extern "C"{
#include "flash.h"
#include "gpio.h"
#include "gpioproto.h"
#include "i2c.h"
#include "pwm.h"
#include "spi.h"
#include "usart.h"
#undef USBIF
#define USBIF IGPIO
@@ -38,6 +41,7 @@ extern volatile uint32_t Tms;
static uint8_t curbuf[MAXSTRLEN]; // buffer for receiving data from USART etc
static uint8_t usart_text = 0; // ==1 for text USART proto
static uint8_t hex_input_mode = 0; // ==0 for text input, 1 for HEX + text in quotes
// TODO: add analog threshold!
@@ -45,23 +49,30 @@ static uint8_t usart_text = 0; // ==1 for text USART proto
#define COMMAND_TABLE \
COMMAND(canspeed, "CAN bus speed setter/getter (kBaud, 10..1000)") \
COMMAND(curcanspeed,"current CAN bus speed (interface speed, not settings)") \
COMMAND(dumpconf, "dump current configuration") \
COMMAND(curpinconf, "dump current (maybe wrong) pin configuration") \
COMMAND(dumpconf, "dump global configuration") \
COMMAND(eraseflash, "erase full flash storage") \
COMMAND(help, "show this help") \
COMMAND(hexinput, "input is text (0) or hex + text in quotes (1)") \
COMMAND(iic, "write data over I2C: I2C=addr data (hex)") \
COMMAND(iicread, "I2C read: I2Cread=addr nbytes (hex)") \
COMMAND(iicreadreg, "I2C read register: I2Creadreg=addr reg nbytes (hex)") \
COMMAND(iicscan, "Scan I2C bus for devices") \
COMMAND(mcutemp, "get MCU temperature (degC*10)") \
COMMAND(mcureset, "reset MCU") \
COMMAND(PA, "GPIOA setter/getter (type PA0=help for further info)") \
COMMAND(PB, "GPIOB setter/getter") \
COMMAND(readconf, "re-read config from flash") \
COMMAND(pinout, "list pinout with all available functions (or selected in setter, like pinout=USART,AIN") \
COMMAND(pwmmap, "show pins with PWM ability") \
COMMAND(readconf, "re-read config from flash") \
COMMAND(reinit, "apply pin config") \
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(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(vdd, "get approx Vdd value (V*100)") \
COMMAND(USART, "Read USART data or send (USART=hex)")
// COMMAND(usartconf, "set USART params (e.g. usartconf=115200 8N1)")
COMMAND(USART, "Read USART data or send (USART=hex)") \
COMMAND(vdd, "get approx Vdd value (V*100)")
// COMMAND(SPI, "Read SPI data or send (SPI=hex)")
// COMMAND(spiconf, "set SPI params")
@@ -102,13 +113,16 @@ enum MiscValues{
MISC_THRESHOLD,
MISC_SPEED,
MISC_TEXT,
MISC_BIN
MISC_HEX,
MISC_LSBFIRST,
MISC_CPOL,
MISC_CPHA,
};
// TODO: add HEX input?
#define KEYWORDS \
KW(AIN) \
KW(AIN) \
KW(IN) \
KW(OUT) \
KW(AF) \
@@ -124,13 +138,18 @@ KW(AIN) \
KW(THRESHOLD) \
KW(SPEED) \
KW(TEXT) \
KW(BIN) \
KW(HEX) \
KW(PWM) \
KW(LSBFIRST) \
KW(CPOL) \
KW(CPHA)
enum{ // indexes of string keywords
typedef enum{ // indexes of string keywords
#define KW(k) STR_ ## k,
KEYWORDS
#undef KW
};
} kwindex_t;
// strings for keywords
static const char *str_keywords[] = {
@@ -153,11 +172,15 @@ static const Keyword keywords[] = {
KEY(USART, GROUP_FUNC, FUNC_USART)
KEY(SPI, GROUP_FUNC, FUNC_SPI)
KEY(I2C, GROUP_FUNC, FUNC_I2C)
KEY(PWM, GROUP_FUNC, FUNC_PWM)
KEY(MONITOR, GROUP_MISC, MISC_MONITOR)
KEY(THRESHOLD, GROUP_MISC, MISC_THRESHOLD)
KEY(SPEED, GROUP_MISC, MISC_SPEED)
KEY(TEXT, GROUP_MISC, MISC_TEXT)
KEY(BIN, GROUP_MISC, MISC_BIN)
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]))
@@ -170,23 +193,30 @@ static const char* errtxt[ERR_AMOUNT] = {
[ERR_WRONGLEN] = "WRONGLEN",
[ERR_CANTRUN] = "CANTRUN",
[ERR_BUSY] = "BUSY",
[ERR_OVERFLOW] = "OVERFLOW",
};
static const char *pinhelp =
"Pin settings: PXx = MODE PULL OTYPE FUNC MISC (in any sequence), where\n"
" MODE: AIN, IN or OUT (analog in, digital in, output)\n"
" MODE: AIN, IN or OUT (analog in, digital in, output), also AF (automatically set when AF selected)\n"
" PULL: PU, PD or FL (pullup, pulldown, no pull - floating)\n"
" OTYPE: PP or OD (push-pull or open-drain)\n"
" FUNC: USART or SPI (enable alternate function and configure peripheal)\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"
" TEXT - USART means data as text ('\n'-separated strings)\n"
" BIN - USART means data as binary (output: HEX)\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"
;
static const char *EQ = " = "; // equal sign for getters
// token delimeters in setters
static const char *DELIM_ = " ,";
static const char *COMMA = ", "; // comma before next val in list
// send `command = `
#define CMDEQ() do{SEND(cmd); SEND(EQ);}while(0)
@@ -235,6 +265,47 @@ static bool argsvals(char *args, int32_t *parno, int32_t *parval){
return false;
}
/**
* @brief parse_hex_data - data parsing in case of `hex + text` input format
* @param input - input string
* @param output - output data
* @param max_len - length of `output`
* @return amount of parsed bytes or -1 in case of overflow or error
*/
static int parse_hex_data(char *input, uint8_t *output, int max_len){
if(!input || !*input || !output || max_len < 1) return 0;
char *p = input;
int out_idx = 0;
while(*p && out_idx < max_len){
while(*p == ' ' || *p == ',') ++p; // omit spaces and commas as delimeters
if(*p == '\0') break; // EOL
if(*p == '"'){ // TEXT (start/end)
++p;
while(*p && *p != '"'){
if(out_idx >= max_len) return -1;
output[out_idx++] = *p++;
}
if(*p == '"'){
++p; // go to next symbol after closing quotation mark
}else return -1; // no closing
}else{ // HEX number
char *start = p;
while(*p && *p != ' ' && *p != ',' && *p != '"') ++p;
char saved = *p;
*p = '\0'; // temporarily for `gethex`
uint32_t val;
const char *end = gethex(start, &val);
if(end != p || val > 0xFF){ // not a hex number or have more than 2 symbols
*p = saved;
return -1;
}
*p = saved;
output[out_idx++] = (uint8_t)val;
}
}
return out_idx;
}
// `port` and `pin` are checked in `parse_pin_command`
// `PAx = ` also printed there
static void pin_getter(uint8_t port, uint8_t pin){
@@ -250,31 +321,41 @@ static void pin_getter(uint8_t port, uint8_t pin){
// `port` and `pin` are checked in `parse_pin_command`
// set GPIO values (if *setter is 0/1) or configure it
static errcodes_t pin_setter(uint8_t port, uint8_t pin, char *setter){
char _1st = *setter;
if(_1st == '0' || _1st == '1'){ // just set/clear pin state; throw out all text after "1"/"0"
DBG("set pin\n");
if(pin_out(port, pin, _1st - '0')) return ERR_OK;
return ERR_CANTRUN;
}
if(strncmp(setter, "help", 4) == 0){ // send PIN help
SENDn(pinhelp);
return ERR_AMOUNT;
}
pinconfig_t curconf;
if(!get_curpinconf(port, pin, &curconf)) return ERR_BADVAL; // copy current config
uint32_t U32;
char *end = getnum(setter, &U32);
if(end != setter && *end == 0){ // number -> set pin/PWM value
if(U32 > 0xff) return ERR_BADVAL;
uint8_t val = (uint8_t) U32;
if(curconf.mode == MODE_OUTPUT){ // set/clear pin
if(U32 > 1) U32 = 1;
DBG("set pin\n");
if(pin_out(port, pin, val)) return ERR_OK;
return ERR_CANTRUN;
}else if(curconf.mode == MODE_AF && curconf.af == FUNC_PWM){
if(setPWM(port, pin, val)) return ERR_OK;
return ERR_CANTRUN;
}
}
// complex setter: parse properties
uint8_t mode_set = 0xFF, pull_set = 0xFF, otype_set = 0xFF, func_set = 0xFF;
bool monitor = false;
uint16_t *pending_u16 = NULL; // pointer to uint16_t value, if !NULL, next token should be a number
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;
pinconfig_t curconf;
if(!get_curpinconf(port, pin, &curconf)) return ERR_BADVAL; // copy current config
#define DELIM_ " ,"
char *saveptr, *token = strtok_r(setter, DELIM_, &saveptr);
while(token){
if(pending_u16){
uint32_t val;
char *end = getnum(token, &val);
end = getnum(token, &val);
if(end == token || val > 0xFFFF) return ERR_BADVAL;
*pending_u16 = (uint16_t)val;
pending_u16 = NULL; // reset
@@ -283,7 +364,7 @@ static errcodes_t pin_setter(uint8_t port, uint8_t pin, char *setter){
}
if(pending_u32){
uint32_t val;
char *end = getnum(token, &val);
end = getnum(token, &val);
if(end == token) return ERR_BADVAL;
*pending_u32 = val;
pending_u32 = NULL;
@@ -324,14 +405,23 @@ static errcodes_t pin_setter(uint8_t port, uint8_t pin, char *setter){
pending_u16 = &curconf.threshold;
break;
case MISC_SPEED:
pending_u32 = &UsartConf.speed;
pending_u32 = &wU32;
break;
case MISC_TEXT: // what to do, if textproto is set, but user wants binary?
UsartConf.textproto = 1;
break;
case MISC_BIN: // clear text flag
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;
}
@@ -345,7 +435,21 @@ static errcodes_t pin_setter(uint8_t port, uint8_t pin, char *setter){
// check periferial settings before refresh pin data
// check current USART settings
if(func_set == FUNC_USART && !chkusartconf(&UsartConf)) return ERR_BADVAL;
if(func_set == FUNC_USART){
if(wU32 != UINT32_MAX) UsartConf.speed = wU32;
if(!chkusartconf(&UsartConf)) return ERR_BADVAL;
}else if(func_set == FUNC_I2C){ // check speed
if(wU32 != UINT32_MAX){
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.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
// set defaults
@@ -357,7 +461,7 @@ static errcodes_t pin_setter(uint8_t port, uint8_t pin, char *setter){
curconf.pull = static_cast <pinpull_t> (pull_set);
curconf.otype = static_cast <pinout_t> (otype_set);
curconf.speed = SPEED_MEDIUM;
curconf.af = func_set;
curconf.af = static_cast <funcnames_t> (func_set);
curconf.monitor = monitor;
if(!set_pinfunc(port, pin, &curconf)) return ERR_BADVAL;
return ERR_OK;
@@ -401,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;
@@ -424,31 +523,22 @@ static errcodes_t cmd_curcanspeed(const char *cmd, char _U_ *args){
return ERR_AMOUNT;
}
static errcodes_t cmd_dumpconf(const char _U_ *cmd, char _U_ *args){
SEND("userconf_sz="); SEND(u2str(the_conf.userconf_sz));
SEND("\ncurrentconfidx="); SENDn(i2str(currentconfidx));
for(int i = 0; i < InterfacesAmount; ++i){
SEND("interface"); PUTCHAR('0' + i);
PUTCHAR('=');
int l = the_conf.iIlengths[i] / 2;
char *ptr = (char*) the_conf.iInterface[i];
for(int j = 0; j < l; ++j){
PUTCHAR(*ptr);
ptr += 2;
}
NL();
}
SEND("canspeed="); SENDn(u2str(the_conf.CANspeed));
SEND("Pin configuration:\n");
// dump global pin config (current == 0) or current (==1)
static void dumppinconf(int current){
if(current) SEND("Current p");
else PUTCHAR('P');
SEND("in configuration:\n");
#define S(k) SEND(str_keywords[STR_ ## k])
#define SP(k) do{PUTCHAR(' '); S(k);}while(0)
for(int port = 0; port < 2; port++){
char port_letter = (port == 0) ? 'A' : 'B';
for(int pin = 0; pin < 16; pin++){
pinconfig_t *p = &the_conf.pinconfig[port][pin];
pinconfig_t cur, *p = &cur;
if(current && !get_curpinconf(port, pin, &cur)) continue; // local
if(!current) p = &the_conf.pinconfig[port][pin]; // global
if(!p->enable) continue;
PUTCHAR('P'); PUTCHAR(port_letter); SEND(i2str(pin)); SEND(EQ);
switch(p->mode){
#define S(k) SEND(str_keywords[STR_ ## k])
#define SP(k) do{PUTCHAR(' '); S(k);}while(0)
case MODE_INPUT:
S(IN);
if(p->pull == PULL_UP) SP(PU);
@@ -475,6 +565,7 @@ static errcodes_t cmd_dumpconf(const char _U_ *cmd, char _U_ *args){
case FUNC_USART: S(USART); break;
case FUNC_SPI: S(SPI); break;
case FUNC_I2C: S(I2C); break;
case FUNC_PWM: S(PWM); break;
default: SEND("UNKNOWN_AF");
}
break;
@@ -493,6 +584,34 @@ static errcodes_t cmd_dumpconf(const char _U_ *cmd, char _U_ *args){
NL();
}
}
#undef S
#undef SP
}
static errcodes_t cmd_curpinconf(const char _U_ *cmd, char _U_ *args){
dumppinconf(TRUE);
return ERR_AMOUNT;
}
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);
PUTCHAR('=');
int l = the_conf.iIlengths[i] / 2;
char *ptr = (char*) the_conf.iInterface[i];
for(int j = 0; j < l; ++j){
PUTCHAR(*ptr);
ptr += 2;
}
NL();
}
SEND("canspeed="); SENDn(u2str(the_conf.CANspeed));
dumppinconf(FALSE); // global pin config
#define S(k) SEND(str_keywords[STR_ ## k])
#define SP(k) do{PUTCHAR(' '); S(k);}while(0)
usartconf_t U = the_conf.usartconfig;
if(U.RXen || U.TXen){ // USART enabled -> tell config
S(USART); SEND(EQ);
@@ -503,44 +622,30 @@ static errcodes_t cmd_dumpconf(const char _U_ *cmd, char _U_ *args){
else if(!U.TXen && U.RXen) SEND(" RXONLY");
NL();
}
// here are usart/spi/i2c configurations
#if 0
bool usart_enabled = false;
for (int port = 0; port < 2 && !usart_enabled; port++) {
for (int pin = 0; pin < 16; pin++) {
pinconfig_t *p = &the_conf.pinconfig[port][pin];
if (p->enable && p->mode == MODE_AF && p->af == FUNC_USART) {
usart_enabled = true;
break;
}
if(I2C1->CR1 & I2C_CR1_PE){ // I2C active, show its speed
S(I2C); SEND(EQ); S(SPEED); PUTCHAR(' ');
switch(the_conf.I2Cspeed){
case 0: SEND("10kHz"); break;
case 1: SEND("100kHz"); break;
case 2: SEND("400kHz"); break;
case 3: SEND("1MHz"); break;
default: SEND("unknown");
}
}
if (usart_enabled) {
SEND("usart=");
// usart_config (baud, bits, parity, stopbits)
// e.g: SEND(u2str(usart_config.baudrate)); SEND(" ");
// SEND(i2str(usart_config.databits)); PUTCHAR(usart_config.parity); SEND(i2str(usart_config.stopbits));
NL();
}
bool spi_enabled = false;
for(int port = 0; port < 2 && !spi_enabled; port++){
for (int pin = 0; pin < 16; pin++) {
pinconfig_t *p = &the_conf.pinconfig[port][pin];
if (p->enable && p->mode == MODE_AF && p->af == FUNC_SPI) {
spi_enabled = true;
break;
}
}
}
if (spi_enabled) {
SEND("spi=");
// spi_config (speed, mode)
if(SPI1->CR1 & SPI_CR1_SPE){
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();
}
#endif
return ERR_AMOUNT;
#undef S
#undef SP
return ERR_AMOUNT;
}
static errcodes_t cmd_setiface(const char* cmd, char *args){
@@ -574,8 +679,17 @@ static errcodes_t cmd_sendcan(const char _U_ *cmd, char *args){
if(!args) return ERR_BADVAL;
char *setter = splitargs(args, NULL);
if(!setter) return ERR_BADVAL;
if(USB_sendstr(ICAN, setter)) return ERR_OK;
USB_putbyte(ICAN, '\n');
if(hex_input_mode){
int len = parse_hex_data(setter, curbuf, MAXSTRLEN);
if(len < 0) return ERR_BADVAL;
if(len == 0) return ERR_AMOUNT;
if(USB_send(ICAN, curbuf, len)) return ERR_OK;
}else{
if(USB_sendstr(ICAN, setter)){
USB_putbyte(ICAN, '\n');
return ERR_OK;
}
}
return ERR_CANTRUN;
}
@@ -627,6 +741,36 @@ static errcodes_t cmd_help(const char _U_ *cmd, char _U_ *args){
return ERR_AMOUNT;
}
static errcodes_t cmd_hexinput(const char *cmd, char *args){
int32_t val;
if(argsvals(args, NULL, &val)){
if(val == 0 || val == 1) hex_input_mode = (uint8_t)val;
else return ERR_BADVAL;
}
CMDEQ();
SENDn(hex_input_mode ? "1" : "0");
return ERR_AMOUNT;
}
static errcodes_t cmd_pwmmap(const char _U_ *cmd, char _U_ *args){
pwmtimer_t t;
SEND("PWM pins:\n");
for(int port = 0; port < 2; ++port){
for(int pin = 0; pin < 16; ++pin){
if(!canPWM(port, pin, &t)) continue;
SEND((port == 0) ? "PA" : "PB");
SEND(u2str(pin));
if(t.collision){
SEND(" conflicts with ");
SEND((t.collport == 0) ? "PA" : "PB");
SEND(u2str(t.collpin));
}
NL();
}
}
return ERR_AMOUNT;
}
static int sendfun(const char *s){
if(!s) return 0;
return USB_sendstr(IGPIO, s);
@@ -637,7 +781,7 @@ static void sendusartdata(const uint8_t *buf, int len){
SEND(str_keywords[STR_USART]); SEND(EQ);
if(usart_text){
USB_send(IGPIO, curbuf, len);
if(curbuf[len-1] != '\n') NL();
NL(); // always add newline at the end to mark real newline ("\n\n") and piece of line ("\n")
}else{
NL();
hexdump(sendfun, (uint8_t*)curbuf, len);
@@ -649,14 +793,17 @@ static errcodes_t cmd_USART(const char _U_ *cmd, char *args){
char *setter = splitargs(args, NULL);
if(setter){
DBG("Try to send over USART\n");
int l = strlen(setter);
if(usart_text){ // add '\n' as we removed it @ parser
if(setter[l-1] != '\n') setter[l++] = '\n';
if(hex_input_mode){
int len = parse_hex_data(setter, curbuf, MAXSTRLEN);
if(len < 0) return ERR_BADVAL;
if(len > 0) return usart_send(curbuf, len);
}else{ // text mode: "AS IS"
int l = strlen(setter);
if(usart_text){ // add '\n' as we removed it @ parser
setter[l++] = '\n';
}
return usart_send((uint8_t*)setter, l);
}
l = usart_send((uint8_t*)setter, l);
if(l < 0) return ERR_BUSY;
else if(l == 0) return ERR_CANTRUN;
return ERR_OK;
} // getter: try to read
int l = usart_receive(curbuf, MAXSTRLEN);
if(l < 0) return ERR_CANTRUN;
@@ -665,12 +812,184 @@ static errcodes_t cmd_USART(const char _U_ *cmd, char *args){
return ERR_AMOUNT;
}
static errcodes_t cmd_iic(const char _U_ *cmd, char *args){
if(!(I2C1->CR1 & I2C_CR1_PE)) return ERR_CANTRUN;
if(!args) return ERR_BADVAL;
char *setter = splitargs(args, NULL);
if(!setter) return ERR_BADVAL;
int len = parse_hex_data(setter, curbuf, MAXSTRLEN);
if(len < 1) return ERR_BADVAL; // need at least address
uint8_t addr = curbuf[0];
if(addr > 0x7F) return ERR_BADVAL; // 7-ÂÉÔÎÙÊ ÁÄÒÅÓ
if(len == 1){ // only address without data
return ERR_BADPAR;
}
addr <<= 1; // roll address to run i2c_write
if(!i2c_write(addr, curbuf + 1, len - 1)){ // len = address + data length
return ERR_CANTRUN;
}
return ERR_OK;
}
static errcodes_t cmd_iicread(const char *cmd, char *args){
if(!(I2C1->CR1 & I2C_CR1_PE)) return ERR_CANTRUN;
if(!args) return ERR_BADVAL;
char *setter = splitargs(args, NULL);
if(!setter) return ERR_BADVAL;
int len = parse_hex_data(setter, curbuf, MAXSTRLEN);
if(len != 2) return ERR_BADVAL; // address, amount of bytes
uint8_t addr = curbuf[0];
uint8_t nbytes = curbuf[1];
if(addr > 0x7F) return ERR_BADVAL; // allow to "read" 0 bytes (just get ACK)
addr <<= 1;
if(!i2c_read(addr, curbuf, nbytes)) return ERR_CANTRUN;
CMDEQ();
if(nbytes > 8) NL();
hexdump(sendfun, curbuf, nbytes);
return ERR_AMOUNT;
}
static errcodes_t cmd_iicreadreg(const char *cmd, char *args){
if(!(I2C1->CR1 & I2C_CR1_PE)) return ERR_CANTRUN;
if(!args) return ERR_BADVAL;
char *setter = splitargs(args, NULL);
if(!setter) return ERR_BADVAL;
int len = parse_hex_data(setter, curbuf, MAXSTRLEN);
if(len != 3) return ERR_BADVAL; // address, register, amount of bytes
uint8_t addr = curbuf[0];
uint8_t nreg = curbuf[1];
uint8_t nbytes = curbuf[2];
if(addr > 0x7F || nbytes == 0) return ERR_BADVAL;
addr <<= 1;
if(!i2c_read_reg(addr, nreg, curbuf, nbytes)) return ERR_CANTRUN;
CMDEQ();
if(nbytes > 8) NL();
hexdump(sendfun, curbuf, nbytes);
return ERR_AMOUNT;
}
static errcodes_t cmd_iicscan(const char _U_ *cmd, char _U_ *args){
if(!(I2C1->CR1 & I2C_CR1_PE)) return ERR_CANTRUN;
i2c_init_scan_mode();
return ERR_OK;
}
// array for `cmd_pinout`
static kwindex_t func_array[FUNC_AMOUNT] = {
[FUNC_AIN] = STR_AIN,
[FUNC_USART] = STR_USART,
[FUNC_SPI] = STR_SPI,
[FUNC_I2C] = STR_I2C,
[FUNC_PWM] = STR_PWM,
};
static errcodes_t cmd_pinout(const char _U_ *cmd, char *args){
uint8_t listmask = 0xff; // bitmask for funcnames_t
if(args && *args){ // change listmask by user choise
char *setter = splitargs(args, NULL);
if(!setter) return ERR_BADVAL;
char *saveptr, *token = strtok_r(setter, DELIM_, &saveptr);
listmask = 0;
while(token){
int i = 0;
for(; i < FUNC_AMOUNT; ++i){
if(0 == strcmp(token, str_keywords[func_array[i]])){
listmask |= (1 << i);
break;
}
}
if(i == FUNC_AMOUNT) return ERR_BADVAL; // wrong argument
token = strtok_r(NULL, DELIM_, &saveptr);
}
if(listmask == 0) return ERR_BADVAL;
}
pwmtimer_t tp; // timers' pins
usart_props_t up; // USARTs' pins
i2c_props_t ip; // I2C's pins
spi_props_t sp;
SEND("\nConfigurable pins (check collisions if functions have same name!):\n");
for(int port = 0; port < 2; ++port){
for(int pin = 0; pin < 16; ++pin){
int funcs = pinfuncs(port, pin);
if(funcs == -1) continue;
uint8_t mask = (static_cast <uint8_t> (funcs)) & listmask;
if(listmask != 0xff && !mask) continue; // no asked functions
SEND((port == 0) ? "PA" : "PB");
SEND(u2str(pin));
SEND(": ");
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);
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);
COMMA(); SEND(str_keywords[STR_SPI]); PUTCHAR('1' + idx);
PUTCHAR('_');
if(sp.ismiso) SEND("MISO");
else if(sp.ismosi) SEND("MOSI");
else SEND("SCK");
}
if(mask & (1 << FUNC_I2C)){
int idx = get_i2c_index(port, pin, &ip);
COMMA(); SEND(str_keywords[STR_I2C]); PUTCHAR('1' + idx);
PUTCHAR('_');
SEND(ip.isscl ? "SCL" : "SDA");
}
if(mask & (1 << FUNC_PWM)){
canPWM(port, pin, &tp);
COMMA(); SEND(str_keywords[STR_PWM]);
SEND(u2str(tp.timidx)); // timidx == TIMNO!
PUTCHAR('_');
PUTCHAR('1' + tp.chidx);
}
NL();
}
}
return ERR_AMOUNT;
#undef COMMA
}
static errcodes_t cmd_SPI(const char *cmd, char *args){
if(!args) return ERR_BADVAL;
if(!(SPI1->CR1 & SPI_CR1_SPE)) return ERR_CANTRUN;
char *setter = splitargs(args, NULL);
if(!setter) return ERR_BADVAL;
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;
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;
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){
return *str ? hash(str + 1, h + ((h << 7) ^ *str)) : h;
}
// TODO: add checking real command length!
static const char *CommandParser(char *str){
char command[CMD_MAXLEN+1];
int i = 0;
@@ -704,10 +1023,27 @@ void GPIO_process(){
l = usart_process(curbuf, MAXSTRLEN);
if(l > 0) sendusartdata(curbuf, l);
l = RECV((char*)curbuf, MAXSTRLEN);
if(l == 0) return;
if(l < 0) SEND("ERROR: USB buffer overflow or string was too long\n");
else{
const char *ans = CommandParser((char*)curbuf);
if(ans) SENDn(ans);
if(l){
if(l < 0) SEND("ERROR: USB buffer overflow or string was too long\n");
else{
const char *ans = CommandParser((char*)curbuf);
if(ans) SENDn(ans);
}
}
if(I2C_scan_mode){
uint8_t addr;
if(i2c_scan_next_addr(&addr)){
SEND("foundaddr = ");
printuhex(addr);
NL();
}
}
}
// starting init by flash settings
void GPIO_init(){
gpio_reinit();
pwm_setup();
usartconf_t usc;
if(get_curusartconf(&usc)) usart_text = usc.textproto;
}

View File

@@ -27,6 +27,7 @@ typedef enum{
ERR_WRONGLEN, // wrong message length
ERR_CANTRUN, // can't run given command due to bad parameters or other
ERR_BUSY, // target interface busy, try later
ERR_OVERFLOW, // string was too long -> overflow
ERR_AMOUNT // amount of error codes or "send nothing"
} errcodes_t;
@@ -39,3 +40,4 @@ typedef enum{
#define ADC_THRES_DEFAULT 100
void GPIO_process();
void GPIO_init();

View File

@@ -17,12 +17,13 @@
*/
#include "adc.h"
#include "gpio.h"
#include "gpioproto.h"
#include "hardware.h"
const uint32_t peripherial_clock = 48000000;
uint8_t ledsON = 0;
TRUE_INLINE void gpio_setup(){ // setup some common GPIO
TRUE_INLINE void pins_setup(){ // setup some common GPIO
// Set LEDS (PB15/PA8) as output
pin_set(LED0_port, LED0_pin); // clear LEDs
pin_set(LED1_port, LED1_pin);
@@ -35,11 +36,13 @@ TRUE_INLINE void gpio_setup(){ // setup some common GPIO
void hardware_setup(){
// enable all active GPIO clocking
RCC->AHBENR |= RCC_AHBENR_GPIOAEN | RCC_AHBENR_GPIOBEN | RCC_AHBENR_DMA1EN;
RCC->APB2ENR |= RCC_APB2ENR_USART1EN | RCC_APB2ENR_SYSCFGEN;
RCC->APB1ENR |= RCC_APB1ENR_USART2EN;
gpio_setup();
//gpio_reinit();
RCC->APB2ENR |= RCC_APB2ENR_USART1EN | RCC_APB2ENR_SYSCFGEN | RCC_APB2ENR_TIM1EN | RCC_APB2ENR_TIM16EN |
RCC_APB2ENR_SPI1EN;
RCC->APB1ENR |= RCC_APB1ENR_USART2EN | RCC_APB1ENR_TIM2EN | RCC_APB1ENR_TIM3EN | RCC_APB1ENR_TIM14EN |
RCC_APB1ENR_I2C1EN;
pins_setup();
adc_setup();
GPIO_init();
}
void iwdg_setup(){

View File

@@ -41,7 +41,7 @@
extern volatile uint32_t Tms;
extern const uint32_t peripherial_clock;
extern uint8_t ledsON;
void hardware_setup();

View File

@@ -0,0 +1,214 @@
/*
* This file is part of the usbcangpio project.
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "gpio.h"
#include "i2c.h"
// fields position in I2C1->TIMINGR
#define I2C_TIMINGR_PRESC_Pos 28
#define I2C_TIMINGR_SCLDEL_Pos 20
#define I2C_TIMINGR_SDADEL_Pos 16
#define I2C_TIMINGR_SCLH_Pos 8
#define I2C_TIMINGR_SCLL_Pos 0
i2c_speed_t curI2Cspeed = I2C_SPEED_10K;
extern volatile uint32_t Tms;
static uint32_t cntr;
volatile uint8_t I2C_scan_mode = 0; // == 1 when I2C is in scan mode
static uint8_t i2caddr = I2C_ADDREND; // address for `scan`, not active
void i2c_setup(i2c_speed_t speed){
if(speed >= I2C_SPEED_1M) speed = curI2Cspeed;
else curI2Cspeed = speed;
uint8_t PRESC, SCLDEL = 4, SDADEL = 2, SCLH, SCLL;
I2C1->CR1 = 0;
// I2C
RCC->CFGR3 |= RCC_CFGR3_I2C1SW; // use sysclock for timing
switch(curI2Cspeed){
case I2C_SPEED_10K:
PRESC = 0x0B;
SCLH = 0xC3;
SCLL = 0xC7;
break;
case I2C_SPEED_100K:
PRESC = 0x0B;
SCLH = 0x0F;
SCLL = 0x13;
break;
case I2C_SPEED_400K:
SDADEL = 3;
SCLDEL = 3;
PRESC = 5;
SCLH = 3;
SCLL = 9;
break;
case I2C_SPEED_1M:
default:
SDADEL = 0;
SCLDEL = 1;
PRESC = 5;
SCLH = 1;
SCLL = 3;
break;
}
I2C1->TIMINGR = (PRESC<<I2C_TIMINGR_PRESC_Pos) | (SCLDEL<<I2C_TIMINGR_SCLDEL_Pos) |
(SDADEL<<I2C_TIMINGR_SDADEL_Pos) | (SCLH<<I2C_TIMINGR_SCLH_Pos) | (SCLL<< I2C_TIMINGR_SCLL_Pos);
if(speed < I2C_SPEED_400K){
SYSCFG->CFGR1 &= ~SYSCFG_CFGR1_I2C_FMP_I2C1;
}else{ // activate FM+
SYSCFG->CFGR1 |= SYSCFG_CFGR1_I2C_FMP_I2C1;
}
I2C1->ICR = 0xffff; // clear all errors
I2C1->CR1 = I2C_CR1_PE;
}
void i2c_stop(){
I2C1->CR1 = 0;
}
/**
* write command byte to I2C
* @param addr - device address (TSYS01_ADDR0 or TSYS01_ADDR1)
* @param data - bytes to write
* @param nbytes - amount of bytes to write
* @param stop - to set STOP
* @return 0 if error
*/
static uint8_t i2c_writes(uint8_t addr, uint8_t *data, uint8_t nbytes, uint8_t stop){
cntr = Tms;
I2C1->CR1 = 0; // clear busy flag
I2C1->ICR = 0x3f38; // clear all errors
I2C1->CR1 = I2C_CR1_PE;
while(I2C1->ISR & I2C_ISR_BUSY){
IWDG->KR = IWDG_REFRESH;
if(Tms - cntr > I2C_TIMEOUT){
DBG("Line busy\n");
return 0; // check busy
}}
cntr = Tms;
while(I2C1->CR2 & I2C_CR2_START){
IWDG->KR = IWDG_REFRESH;
if(Tms - cntr > I2C_TIMEOUT){
return 0; // check start
}}
//I2C1->ICR = 0x3f38; // clear all errors
I2C1->CR2 = nbytes << 16 | addr;
if(stop) I2C1->CR2 |= I2C_CR2_AUTOEND; // autoend
// now start transfer
I2C1->CR2 |= I2C_CR2_START;
for(int i = 0; i < nbytes; ++i){
cntr = Tms;
while(!(I2C1->ISR & I2C_ISR_TXIS)){ // ready to transmit
IWDG->KR = IWDG_REFRESH;
if(I2C1->ISR & I2C_ISR_NACKF){
I2C1->ICR |= I2C_ICR_NACKCF;
DBG("NAK\n");
return 0;
}
if(Tms - cntr > I2C_TIMEOUT){
DBG("Timeout\n");
return 0;
}
}
I2C1->TXDR = data[i]; // send data
}
// wait for data gone
while(I2C1->ISR & I2C_ISR_BUSY){
IWDG->KR = IWDG_REFRESH;
if(Tms - cntr > I2C_TIMEOUT){break;}
}
return 1;
}
uint8_t i2c_write(uint8_t addr, uint8_t *data, uint8_t nbytes){
return i2c_writes(addr, data, nbytes, 1);
}
/**
* read nbytes of data from I2C line
* `data` should be an array with at least `nbytes` length
* @return 1 if all OK, 0 if NACK or no device found
*/
static uint8_t i2c_readb(uint8_t addr, uint8_t *data, uint8_t nbytes, uint8_t busychk){
if(busychk){
cntr = Tms;
while(I2C1->ISR & I2C_ISR_BUSY){
IWDG->KR = IWDG_REFRESH;
if(Tms - cntr > I2C_TIMEOUT){
DBG("Line busy\n");
return 0; // check busy
}}
}
cntr = Tms;
while(I2C1->CR2 & I2C_CR2_START){
IWDG->KR = IWDG_REFRESH;
if(Tms - cntr > I2C_TIMEOUT){
DBG("No start\n");
return 0; // check start
}}
// read N bytes
I2C1->CR2 = (nbytes<<16) | addr | 1 | I2C_CR2_AUTOEND | I2C_CR2_RD_WRN;
I2C1->CR2 |= I2C_CR2_START;
uint8_t i;
for(i = 0; i < nbytes; ++i){
cntr = Tms;
while(!(I2C1->ISR & I2C_ISR_RXNE)){ // wait for data
IWDG->KR = IWDG_REFRESH;
if(I2C1->ISR & I2C_ISR_NACKF){
I2C1->ICR |= I2C_ICR_NACKCF;
DBG("NAK\n");
return 0;
}
if(Tms - cntr > I2C_TIMEOUT){
DBG("Timeout\n");
return 0;
}
}
*data++ = I2C1->RXDR;
}
return 1;
}
uint8_t i2c_read(uint8_t addr, uint8_t *data, uint8_t nbytes){
return i2c_readb(addr, data, nbytes, 1);
}
// read register reg
uint8_t i2c_read_reg(uint8_t addr, uint8_t reg, uint8_t *data, uint8_t nbytes){
if(!i2c_writes(addr, &reg, 1, 0)) return 0;
return i2c_readb(addr, data, nbytes, 0);
}
void i2c_init_scan_mode(){
i2caddr = 1;
I2C_scan_mode = 1;
}
// return 1 if next addr is active & return in as `addr`
// if addresses are over, return 1 and set addr to I2C_NOADDR
// if scan mode inactive, return 0 and set addr to I2C_NOADDR
int i2c_scan_next_addr(uint8_t *addr){
*addr = i2caddr;
if(i2caddr == I2C_ADDREND){
*addr = I2C_ADDREND;
I2C_scan_mode = 0;
return 0;
}
if(!i2c_read_reg((i2caddr++)<<1, 0, NULL, 0)) return 0;
return 1;
}

View File

@@ -0,0 +1,47 @@
/*
* This file is part of the usbcangpio project.
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stm32f0.h>
#define I2C_ADDREND (0x80)
typedef enum{
I2C_SPEED_10K,
I2C_SPEED_100K,
I2C_SPEED_400K,
I2C_SPEED_1M,
I2C_SPEED_AMOUNT
} i2c_speed_t;
extern i2c_speed_t curI2Cspeed;
extern volatile uint8_t I2C_scan_mode;
// timeout of I2C bus in ms
#define I2C_TIMEOUT (100)
void i2c_setup(i2c_speed_t speed);
void i2c_stop();
uint8_t i2c_read(uint8_t addr, uint8_t *data, uint8_t nbytes);
uint8_t i2c_read_reg(uint8_t addr, uint8_t reg, uint8_t *data, uint8_t nbytes);
uint8_t i2c_write(uint8_t addr, uint8_t *data, uint8_t nbytes);
void i2c_init_scan_mode();
int i2c_scan_next_addr(uint8_t *addr);

View File

@@ -0,0 +1,177 @@
/*
* This file is part of the usbcangpio project.
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stm32f0.h>
#include <string.h>
#include "pwm.h"
static volatile TIM_TypeDef * const timers[TIMERS_AMOUNT] = {
[TIM1_IDX] = TIM1,
[TIM2_IDX] = TIM2,
[TIM3_IDX] = TIM3,
[TIM14_IDX] = TIM14,
[TIM16_IDX] = TIM16,
};
#if 0
PWM (start - collisions):
PxN XY (XY: TIMX_CHY)
PA1 22 *
PA2 23 **
PA3 24 ***
PA6 161
PA7 141
PA9 12
PA10 13
PB0 33
PB1 34
PB3 22 *
PB4 31
PB5 32
PB10 23 **
PB11 24 ***
-> need to set up timers / channels
TIM1 / 2 3
TIM2 / 2 3 4
TIM3 / 1 2 3 4
TIM14 / 1
TIM16 / 1
#endif
#define PT(i, ch) {.timidx = i, .chidx = ch}
#define PTC(i, ch, P, p) {.timidx = i, .chidx = ch, .collision = 1, .collport = P, .collpin = p}
static const pwmtimer_t timer_map[2][16] = {
[0] = {
[1] = PTC(TIM2_IDX, 1, 1, 3),
[2] = PTC(TIM2_IDX, 2, 1, 10),
[3] = PTC(TIM2_IDX, 3, 1, 11),
[6] = PT(TIM16_IDX, 0),
[7] = PT(TIM14_IDX, 0),
[9] = PT(TIM1_IDX, 1),
[10] = PT(TIM1_IDX, 2)
},
[1] = {
[0] = PT(TIM3_IDX, 2),
[1] = PT(TIM3_IDX, 3),
[3] = PTC(TIM2_IDX, 1, 0, 1),
[4] = PT(TIM3_IDX, 0),
[5] = PT(TIM3_IDX, 1),
[10] = PTC(TIM2_IDX, 2, 0, 2),
[11] = PTC(TIM2_IDX, 3, 0, 3)
}
};
#undef PT
#undef PTC
// counter of used channels (0 - timer OFF)
static uint8_t channel_counter[TIMERS_AMOUNT] = {0};
void pwm_setup(){
// setup; start/stop only by user request
for(int i = 1; i < TIMERS_AMOUNT; ++i){ // start from 1 as 0 forbidden
volatile TIM_TypeDef *timer = timers[i];
timer->CR1 = 0;
timer->PSC = 7; // 6MHz for 23.4kHz PWM
timer->ARR = 254; // 255 == 100%
// PWM mode 1, preload enable
timer->CCMR1 = TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1PE |
TIM_CCMR1_OC2M_2 | TIM_CCMR1_OC2M_1 | TIM_CCMR1_OC2PE;
timer->CCMR2 = TIM_CCMR2_OC3M_2 | TIM_CCMR2_OC3M_1 | TIM_CCMR2_OC3PE |
TIM_CCMR2_OC4M_2 | TIM_CCMR2_OC4M_1 | TIM_CCMR2_OC4PE;
timer->BDTR |= TIM_BDTR_MOE; // enable main output (need for some timers)
timer->EGR |= TIM_EGR_UG; // force update generation
}
bzero(channel_counter, sizeof(channel_counter));
}
/**
* @brief canPWM - check if pin have PWM ability
* @param port - port (0/1 for GPIOA/GPIOB)
* @param pin - pin (0..15)
* @param t (o) - struct for pin's PWM timer
* @return TRUE if can, FALSE if no
*/
int canPWM(uint8_t port, uint8_t pin, pwmtimer_t *t){
if(port > 1 || pin > 15) return 0;
if(t) *t = timer_map[port][pin];
if(timer_map[port][pin].timidx == TIM_UNSUPPORTED) return FALSE;
return TRUE;
}
/**
* @brief startPWM - run PWM on given port/pin
* @param port
* @param pin
* @return FALSE if unsupported
*/
int startPWM(uint8_t port, uint8_t pin){
timidx_t idx = timer_map[port][pin].timidx;
if(idx == TIM_UNSUPPORTED) return FALSE;
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
}
return TRUE;
}
// stop given PWM channel and stop timer if there's no used channels
void stopPWM(uint8_t port, uint8_t pin){
timidx_t idx = timer_map[port][pin].timidx;
if(idx == TIM_UNSUPPORTED) return;
volatile TIM_TypeDef *timer = timers[idx];
uint8_t chidx = timer_map[port][pin].chidx;
uint32_t chen = TIM_CCER_CC1E << (chidx<<2);
if(timer->CCER & chen){
if(0 == --channel_counter[idx]) timer->CR1 &= ~TIM_CR1_CEN; // stop timer
timer->CCER &= ~chen;
}
}
/**
* @brief setPWM - set PWM value for given pin on given port
* @param port
* @param pin
* @param val - 0..255
* @return FALSE if pin can't PWM
*/
int setPWM(uint8_t port, uint8_t pin, uint8_t val){
timidx_t idx = timer_map[port][pin].timidx;
if(idx == TIM_UNSUPPORTED) return FALSE;
volatile uint32_t *CCR = &timers[idx]->CCR1 + timer_map[port][pin].chidx;
*CCR = val;
return TRUE;
}
/**
* @brief getPWM - get PWM value for given pin on given port
* @param port
* @param pin
* @return -1 if there's no PWM on that pin
*/
int16_t getPWM(uint8_t port, uint8_t pin){
timidx_t idx = timer_map[port][pin].timidx;
if(idx == TIM_UNSUPPORTED) return -1;
volatile uint32_t *CCR = &timers[idx]->CCR1 + timer_map[port][pin].chidx;
return (int16_t) *CCR;
}

View File

@@ -0,0 +1,49 @@
/*
* This file is part of the usbcangpio project.
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdint.h>
// used timers
typedef enum{
TIM_UNSUPPORTED = 0,
TIM1_IDX,
TIM2_IDX,
TIM3_IDX,
TIM14_IDX,
TIM16_IDX,
TIMERS_AMOUNT
}timidx_t;
// Timers for PWM
typedef struct{
timidx_t timidx : 3; // timer index from array of timers used
uint8_t chidx : 2; // channel index (0..3)
uint8_t collision : 1; // have collision with other channel (1)
uint8_t collport : 1; // collision port index (0 - GPIOA, 1 - GPIOB)
uint8_t collpin : 4; // collision pin index (0..15)
} pwmtimer_t;
void pwm_setup();
int canPWM(uint8_t port, uint8_t pin, pwmtimer_t *t);
int startPWM(uint8_t port, uint8_t pin);
void stopPWM(uint8_t port, uint8_t pin);
int setPWM(uint8_t port, uint8_t pin, uint8_t val);
int16_t getPWM(uint8_t port, uint8_t pin);

View File

@@ -0,0 +1,83 @@
/*
* This file is part of the usbcangpio project.
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stm32f0.h>
#include "flash.h"
#include "hardware.h"
#include "spi.h"
// get best prescaler to fit given frequency
static uint16_t get_baudrate_prescaler(uint32_t speed_hz){
uint32_t freq = peripherial_clock;
uint32_t best_i = 7;
uint32_t best_err = 0xFFFFFFFF;
for(int i = 0; i < 8; i++){
freq >>= 1;
uint32_t err = (freq > speed_hz) ? (freq - speed_hz) : (speed_hz - freq);
if(err < best_err){
best_err = err;
best_i = i;
}else if(err > best_err) break;
}
return best_i;
}
void spi_setup(){
RCC->APB2RSTR |= RCC_APB2RSTR_SPI1RST;
RCC->APB2RSTR = 0;
SPI1->CR1 = 0;
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;
}
void spi_stop(){
SPI1->CR1 &= ~SPI_CR1_SPE;
}
// return -1 if SPI isn't run or got error
int spi_transfer(const uint8_t *tx, uint8_t *rx, int len){
if(len < 1 || !(SPI1->CR1 & SPI_CR1_SPE)) return -1;
int i;
for(i = 0; i < len; ++i){
uint32_t timeout = 1000000;
while(!(SPI1->SR & SPI_SR_TXE)){
if (--timeout == 0) return -1; // error by timeout: TX isn't ready
}
uint8_t out = (tx) ? tx[i] : 0;
*((volatile uint8_t*)&SPI1->DR) = out;
timeout = 1000000;
while(!(SPI1->SR & SPI_SR_RXNE)){
if(--timeout == 0) return 0;
}
uint8_t in = *((volatile uint8_t*)&SPI1->DR);
if(rx) rx[i] = in;
}
//while(SPI1->SR & SPI_SR_BSY){ }
return i;
}

View File

@@ -0,0 +1,25 @@
/*
* This file is part of the usbcangpio project.
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdint.h>
void spi_setup();
void spi_stop();
int spi_transfer(const uint8_t *tx, uint8_t *rx, int len);

View File

@@ -147,7 +147,8 @@ static const char *getdec(const char *buf, uint32_t *N){
return buf;
}
// read hexadecimal number (without 0x prefix!)
static const char *gethex(const char *buf, uint32_t *N){
const char *gethex(const char *buf, uint32_t *N){
if(!buf || !N) return NULL;
const char *start = buf;
uint32_t num = 0;
while(*buf){

View File

@@ -37,12 +37,13 @@
#define REPOURL "https://github.com/eddyem/stm32samples/tree/master/F0:F030,F042,F072/usbcan_gpio " RLSDBG " build #" BUILD_NUMBER "@" BUILD_DATE "\n"
// max string len to '\n' (including '\0')
#define MAXSTRLEN 128
#define MAXSTRLEN 256
void hexdump(int (*sendfun)(const char *s), uint8_t *arr, uint16_t len);
const char *u2str(uint32_t val);
const char *i2str(int32_t i);
const char *uhex2str(uint32_t val);
const char *gethex(const char *buf, uint32_t *N);
char *getnum(const char *txt, uint32_t *N);
char *omit_spaces(const char *buf);
char *getint(const char *txt, int32_t *I);

View File

@@ -20,6 +20,7 @@
#include <string.h>
#include "flash.h"
#include "hardware.h"
#include "ringbuffer.h"
#include "usart.h"
@@ -101,13 +102,12 @@ int usart_config(usartconf_t *uc){
if(curUSARTidx != -1) usart_stop(); // disable previous USART if enabled
uint8_t No = usartconfig.idx;
volatile USART_TypeDef *U = Usarts[No];
uint32_t peripheral_clock = 48000000;
// Disable USART while configuring
U->CR1 = 0;
U->ICR = 0xFFFFFFFF; // Clear all interrupt flags
// Assuming oversampling by 16 (default after reset). For higher baud rates you might use by 8.
U->BRR = peripheral_clock / (usartconfig.speed);
usartconfig.speed= peripheral_clock / U->BRR; // fix for real speed
U->BRR = peripherial_clock / (usartconfig.speed);
usartconfig.speed = peripherial_clock / U->BRR; // fix for real speed
uint32_t cr1 = 0, cr3 = 0;
textformat = usartconfig.textproto;
monitor = usartconfig.monitor;
@@ -209,10 +209,14 @@ int usart_process(uint8_t *buf, int len){
int remained = DMA1_Channel5->CNDTR;
int write_idx = DMARXBUFSZ - remained; // next symbol to be written
int available = (write_idx - dma_read_idx); // length of data available
if(available < 0) available += DMARXBUFSZ; // write to the left of read
if(available == 0){
RXrdy = 0; // clear old ready flag if got no data
return 0;
}
int monitored_len = available;
uint8_t locmonitor = monitor; // if `buf` not pointed, set this flag to zero
if(available < 0) available += DMARXBUFSZ; // write to the left of read
if(available){
if(available > 0){
if(locmonitor){
if(buf && len > 0){
if(len < monitored_len) monitored_len = len;
@@ -222,24 +226,32 @@ int usart_process(uint8_t *buf, int len){
if(available >= (DMARXBUFSZ/2) || RXrdy){ // enough data or lonely couple of bytes but need to show
// copy data in one or two chunks (wrap handling)
int wrOK = FALSE;
// check if we can write to RB `available` bytes
int canRB = TRUE;
if(!locmonitor){
int rballow = RBin.length - 1 - RB_datalen(&RBin);
if(rballow < available) canRB = FALSE;
}
if(dma_read_idx + available <= DMARXBUFSZ){ // head before tail
if(locmonitor){
memcpy(buf, &inbuffer[dma_read_idx], monitored_len);
ret = monitored_len;
wrOK = TRUE;
}else{
if(available == RB_write(&RBin, &inbuffer[dma_read_idx], available)) wrOK = TRUE;
if(canRB && available == RB_write(&RBin, &inbuffer[dma_read_idx], available)) wrOK = TRUE;
else if(buf && len > 0) ret = RB_read(&RBin, buf, len); // ringbuffer overfull -> emerge clearing
}
}else{ // head after tail - two chunks
int first = DMARXBUFSZ - dma_read_idx;
if(locmonitor){
memcpy(buf, &inbuffer[dma_read_idx], first);
memcpy(buf, inbuffer, monitored_len - first);
memcpy(buf + first, inbuffer, monitored_len - first);
ret = monitored_len;
wrOK = TRUE;
}else{
if((first == RB_write(&RBin, &inbuffer[dma_read_idx], first)) &&
if(canRB && (first == RB_write(&RBin, &inbuffer[dma_read_idx], first)) &&
(available - first) == RB_write(&RBin, inbuffer, available - first)) wrOK = TRUE;
else if(buf && len > 0) ret = RB_read(&RBin, buf, len);
}
}
if(wrOK){
@@ -247,29 +259,20 @@ int usart_process(uint8_t *buf, int len){
dma_read_idx = write_idx; // update read pointer
}
}
}else if(available < 0){ // das ist fantastisch!
if(buf && len > 0) ret = RB_read(&RBin, buf, len);
DBG("WTF? USART's `available` < 0!!!\n");
}
#if 0
// Output data
if(TXrdy){ // ready to send new data - here we can process RBout, if have
int got = RB_read...
if(got > 0){ // send next data portion
volatile DMA_Channel_TypeDef *T = cfg->dma_tx_channel;
T->CCR &= ~DMA_CCR_EN;
T->CMAR = (uint32_t) outbuffers[i];
T->CNDTR = got;
TXrdy = 0;
T->CCR |= DMA_CCR_EN; // start new transmission
}
}
#endif
// we can work with RBout to send more than `usart_send` can
// here we can send next data portion
return ret;
}
// send data buffer
int usart_send(const uint8_t *data, int len){
if(curUSARTidx == -1 || !data || len < 1) return 0;
if(TXrdy == 0) return -1;
if(len > DMATXBUFSZ) len = DMATXBUFSZ;
errcodes_t usart_send(const uint8_t *data, int len){
if(curUSARTidx == -1 || !data || len < 1) return ERR_CANTRUN;
if(TXrdy == 0) return ERR_BUSY;
if(len > DMATXBUFSZ) return ERR_OVERFLOW;
memcpy(outbuffer, data, len);
volatile DMA_Channel_TypeDef *T = DMA1_Channel4;
T->CCR &= ~DMA_CCR_EN;
@@ -277,7 +280,7 @@ int usart_send(const uint8_t *data, int len){
T->CNDTR = len;
TXrdy = 0;
T->CCR |= DMA_CCR_EN; // start new transmission
return len;
return ERR_OK;
}
/**

View File

@@ -19,12 +19,13 @@
#pragma once
#include <stdint.h>
#include "gpioproto.h"
// DMA linear buffers for Rx/Tx
#define DMARXBUFSZ 128
#define DMATXBUFSZ 128
#define DMARXBUFSZ 192
#define DMATXBUFSZ 192
// incoming ring buffer - only if there's a lot of data in DMA RX buffer
#define RXRBSZ 256
#define RXRBSZ 384
#define USART_MIN_SPEED 1024
#define USART_MAX_SPEED 1000000
@@ -51,4 +52,4 @@ void get_defusartconf(usartconf_t *c);
int usart_process(uint8_t *buf, int len);
int usart_receive(uint8_t *buf, int len);
int usart_send(const uint8_t *data, int len);
errcodes_t usart_send(const uint8_t *data, 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-14T01:13:52. -->
<!-- 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
@@ -15,9 +16,15 @@ hardware.h
hashgen/Readme
hashgen/hashgen.c
hashgen/mktestdic
i2c.c
i2c.h
main.c
pwm.c
pwm.h
ringbuffer.c
ringbuffer.h
spi.c
spi.h
strfunc.c
strfunc.h
usart.c

View File

@@ -1,2 +1,2 @@
#define BUILD_NUMBER "173"
#define BUILD_DATE "2026-03-14"
#define BUILD_NUMBER "226"
#define BUILD_DATE "2026-03-18"