Compare commits

...

10 Commits

Author SHA1 Message Date
Edward Emelianov
4614183f38 change proto to text strings; add simplest NTC measurement 2026-05-06 23:30:36 +03:00
Edward Emelianov
0b7ecba54b fixed small bug 2026-05-01 23:09:48 +03:00
Edward Emelianov
f74ea14bbe add documentation 2026-04-25 20:46:52 +03:00
Edward Emelianov
40c8f23e36 seems like it works; later TODO: add USART for RS232 2026-04-14 23:42:52 +03:00
c93c2ac6ab add enable/disable command 2026-04-14 09:28:06 +03:00
Edward Emelianov
56701d6ef6 ... 2026-04-11 00:26:47 +03:00
Edward Emelianov
9ef62c0d4d started writting code 2026-04-08 23:48:23 +03:00
0e9c7361b9 fixed stupid bug 2026-03-25 15:08:51 +03:00
Edward Emelianov
8e591d73bd remove flood in case of error 2026-03-23 23:28:22 +03:00
Edward Emelianov
024d256563 fixed USB 2026-03-23 23:08:08 +03:00
87 changed files with 6112 additions and 1205 deletions

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 18.0.2, 2026-03-18T23:46:41. -->
<!-- Written by QtCreator 19.0.0, 2026-03-23T23:04:04. -->
<qtcreator>
<data>
<variable>EnvironmentId</variable>
@@ -155,6 +155,7 @@
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
<value type="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
<valuelist type="QVariantList" key="Analyzer.Valgrind.SuppressionFiles"/>
<value type="QList&lt;int&gt;" key="Analyzer.Valgrind.VisibleErrorKinds"></value>
<valuelist type="QVariantList" key="CustomOutputParsers"/>
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
@@ -190,6 +191,7 @@
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
<value type="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
<valuelist type="QVariantList" key="Analyzer.Valgrind.SuppressionFiles"/>
<value type="QList&lt;int&gt;" key="Analyzer.Valgrind.VisibleErrorKinds"></value>
<valuelist type="QVariantList" key="CustomOutputParsers"/>
<value type="int" key="PE.EnvironmentAspect.Base">2</value>

View File

@@ -0,0 +1,11 @@
BINARY := as3935
# MCU code
MCU ?= F103x6
# change this linking script depending on particular MCU model,
LDSCRIPT ?= stm32f103x6.ld
DEFINES := -DSTM32F10X_MD
include ../makefile.f1
include ../../makefile.stm32
$(OBJDIR)/commproto.o: commproto.cpp $(VERSION_FILE)

View File

@@ -0,0 +1,373 @@
# AS3935-based Lightning Detector
## Overview
This project implements a lightning detection system using two AS3935 Franklin Lightning Sensors interfaced
with an STM32F103 microcontroller. The device communicates over USB CDC (virtual serial port) and exposes a
text-based command protocol for configuration, monitoring, and data extraction.
In the next version it is possible to add RS-232 or RS-485 as an alternative to USB.
Key features:
- Dual AS3935 sensors on single SPI channel.
- On-chip ADC for internal MCU temperature and voltage supply monitoring (more features may be added in the
future).
- Persistent configuration storage in internal Flash memory.
- USB CDC interface with automatic detection of lightning events.
- Extensive command set for real-time tuning and status readout.
---
## Hardware Connections
| Pin | Configuration | Function |
|--------|-----------|----------|
| PA0 | GPIO | INT0 - External interrupt from sensor 0 (pull-down) |
| PA1 | GPIO | INT1 - External interrupt from sensor 1 (pull-down) |
| PA2 | GPIO | CS0 - SPI chip select for sensor 0 |
| PA3 | GPIO | CS1 - SPI chip select for sensor 1 |
| PA5 | SPI1 SCK | SPI clock |
| PA6 | SPI1 MISO | SPI Master In / Slave Out |
| PA7 | SPI1 MOSI | SPI Master Out / Slave In |
| PA9 | USART1 Tx | (unused in this version) |
| PA10 | USART1 Rx | (unused) |
| PA15 | GPIO | USB D+ pull-up control (1.5 kΩ) |
| PC13 | GPIO | On-board LED (active low) |
The two AS3935 sensors share the SPI bus but have separate chip select lines and interrupt pins.
If necessary, it can be expanded to include more sensors.
---
## USB CDC Communication
The device appears as a virtual COM port when connected to a host computer. Communication parameters (baud
rate, parity, etc.) are ignored — the underlying USB bulk transport guarantees reliable delivery.
- **Line coding**: default sent by host (e.g., 115200 8N1) is accepted but not used.
- **Ring buffers**: 1024 bytes each for TX and RX.
- **Command terminator**: every command must end with a newline (`\n`). The response is also terminated by a
newline, except for multi-line outputs (help, dumpconf, SPI).
When the host opens the virtual COM port (DTR asserted), the device becomes ready. If the port is closed, the
`CDCready` flag is cleared and any outgoing data is discarded.
---
## Command Protocol
### General Syntax
```
<command> [<channel>] [ = <value> ]
```
- **command** — case-sensitive name (exactly as listed, all lowercase).
- **channel** — numeric index of the sensor (0 or 1). Required for sensor-specific commands; omitted for
global commands.
- **value** — decimal integer, hexadecimal (`0x` prefix), binary (`0b` prefix), or a string (for commands
like `setiface`). For the `SPI` command, the value is a sequence of hex bytes and/or quoted text.
- **whitespaces** around `=` and between tokens is ignored.
There are two modes:
1. **Getter** — if `=` is absent, the current value is returned:
```
command<channel> = <current_value>
```
No `OK` response follows.
2. **Command** — also don't need `=`, but returns `OK` or error code.
3. **Setter** — if `=` is present, the value after `=` is assigned. The returned value is text describing
error code (`OK` etc.). A getter-style response is **not** returned after a successful set; only `OK`
appears.
For channel-dependent commands the response includes the channel number, e.g.:
```
gain0 = 5
```
Global commands (no channel) use the same format without a number:
```
restonstart = 1
```
Commands that are purely actions (e.g., `clearstat`, `resetdef`) only take a channel number and do not accept
an `=` sign. On success they reply `OK`.
### Error Codes
If a command cannot be executed, one of the following error strings is returned instead of `OK`:
| Error String | Meaning |
|--------------|---------|
| `BADCMD` | Unknown command |
| `BADPAR` | Invalid channel number |
| `BADVAL` | Invalid value for the setter (out of range or malformed) |
| `WRONGLEN` | Buffer overflow or line too long |
| `CANTRUN` | SPI communication failure, sensor not responding, or illegal state |
| `BUSY` | SPI or ring buffer busy - retry later |
| `OVERFLOW` | Input string exceeded maximum length (256 characters) |
### Command Reference
All commands are defined in the source file `commproto.cpp` using macro `COMMAND_TABLE`.
#### Sensor Configuration Commands
These commands read or write parameters of a specific AS3935 sensor. They require the channel number as the
first argument. For details, please refer to the sensor's technical documentation.
| Command | Description | Value Range | Default |
|--------------|-------------|-------------|---------|
| `displco` | Display on IRQ pin: 0=nothing, 1=TRCO, 2=SRCO, 3=LCO | 0 - 3 | 0 |
| `gain` | Amplifier gain (AFE_GB) | 0 - 31 | 18 |
| `lco_fdiv` | Antenna LCO frequency divider | 0 - 3 | 0 |
| `maskdist` | Mask disturbers (1 = mask, 0 = allow) | 0 or 1 | 0 |
| `minnumlig` | Minimum lightning number (0→1, 1→5, 2→9, 3→16) | 0 - 3 | 0 |
| `nflev` | Noise floor level | 0 - 7 | 2 |
| `srej` | Spike rejection | 0 - 15 | 2 |
| `tuncap` | Tune capacitor setting (n·8 pF) | 0 - 15 | 0 |
| `wdthres` | Watchdog threshold | 0 - 15 | 2 |
**Syntax examples:**
```
gain 0 = 12 set gain of sensor 0 to 12
gain 1 read gain of sensor 1 → gain1 = 5
displco 0 = 1 configure sensor 0 IRQ pin to output TRCO clock
displco 0 read display mode → displco0 = 1
```
#### Sensor Action & Status Commands
These commands do not take a value after `=`, only a channel number.
| Command | Description |
|--------------|-------------|
| `clearstat` | Clear the lightning statistics (last 15 min) |
| `distance` | Read estimated distance to the lightning in km (0-63, 63=out of range) |
| `energy` | Read energy of the last lightning (24-bit value) |
| `intcode` | Read the last interrupt code: 1=Noise, 4=Disturber, 8=Lightning |
| `iscalib` | Check if both RCO and TRCO calibrations are done (returns 1 if done) |
| `resetdef` | Reset the sensor to factory default settings |
| `wakeup` | Wake up the sensor and run RCO calibration |
**Examples:**
```
wakeup 0 wake up sensor 0, auto-calibrate → OK
iscalib 0 read calibration status → iscalib0 = 1
energy 0 read energy → energy0 = 123456
distance 1 read distance → distance1 = 12
intcode 0 read interrupt flags → intcode0 = 8
clearstat 0 clear statistics → OK
resetdef 0 reset to defaults → OK
```
#### Global (Non-Sensor) Commands
These commands do not require a channel number.
| Command | Description |
|---------|-------------|
| `dumpconf` | Dump the full current configuration (stored in RAM until saved) |
| `eraseflash` | Erase the entire Flash configuration storage |
| `help` | Print list of all commands and a repository URL |
| `mcureset` | Software reset of the MCU |
| `mcutemp` | MCU internal temperature in tenths of °C (e.g., 287 = 28.7℃) |
| `readconf` | Read sensor configuration from the chip and update RAM for given channel |
| `restonstart` | Restore sensor parameters at start-up (0 or 1) |
| `saveconf` | Save the current RAM configuration to Flash |
| `setiface` | Get/set the USB interface name string (max 16 characters) |
| `time` | Current uptime in milliseconds |
| `vdd` | Supply voltage VDD in hundredths of a volt (e.g., 330 = 3.30V) |
**Examples:**
```
mcutemp → mcutemp = 287
vdd → vdd = 330
setiface = MyDetector set interface name to "MyDetector"
setiface → setiface = MyDetector
restonstart = 1 enable automatic restore at power-up
saveconf save current configuration to Flash
dumpconf print all stored and current parameters
readconf 0 read sensor 0 parameters into RAM (for later save)
```
#### SPI Low-Level Command
The `SPI` command allows arbitrary bidirectional SPI transactions to a selected sensor.
**Syntax:**
```
SPI <channel> = <hexdata>
```
`<hexdata>` is a sequence of bytes expressed as space- or comma-separated hexadecimal numbers (without `0x`
prefix), optionally with quoted text strings. Anything inside double quotes is transmitted as raw ASCII.
**Example:**
```
SPI 0 = 00 01 "hello" 02
```
This sends the bytes `0x00, 0x01, 'h','e','l','l','o', 0x02` to sensor 0 and returns the received bytes as a
hex dump.
**Response format:**
```
SPI0 =
00 01 68 65 6c 6c 6f 02
```
---
## Lightning Interrupts
The main loop continuously monitors the INT pins (PA0, PA1). When a rising edge is detected and the
corresponding `DISPLCO` setting is `0` (not used as clock output), the code reads the interrupt register:
- Disturber and noise events are reported as:
```
INTERRUPT0=NOICE,DISTURBER
```
- A lightning event additionally triggers `lightning_info()`, which prints the energy and distance:
```
INTERRUPT0=LIGHTNING
energy0 = 123456
distance0 = 12
```
If the pin is configured as a clock output (`displco` != 0), no interrupt processing occurs. You can mask
disturber interrupts by command `maskdist n = 0`, in this case only `NOICE` and `LIGHTNING` will be
monitored.
---
## Configuration Storage
User settings are stored in the internal Flash of the STM32F103. The Flash area reserved for configuration
starts at a fixed address (defined in the linker script) and has a limited capacity. The structure
`user_conf` contains:
- `userconf_sz` — magic cookie (size of the structure).
- `iInterface` + `iIlength` — USB interface name (stored as two bytes per character for Unicode).
- `spars[]` — per-sensor parameters (gain, WDTH, NF_LEV, SREJ, MIN_NUM_LIG, MASK_DIST, LCO_FDIV, TUN_CAP).
- `flags.restore` — if 1, all parameters are automatically applied after reset.
**Mechanism:**
- Each time `saveconf` is issued, a new copy of the configuration is appended to the Flash storage. A binary
search at boot finds the last valid entry.
- If the storage becomes full, the next `saveconf` will first erase the entire storage and then write the
current configuration.
- `eraseflash` erases the whole configuration area; `restonstart` controls whether the stored configuration
is uploaded to sensors at start-up.
**Capacity:**
Approximately `(FLASH_SIZE * 1024 - offset) / sizeof(user_conf)` configurations can be stored. For a typical
64 KB device, this is hundreds of entries.
---
## ADC and Internal Sensors
The STM32s ADC continuously samples the internal temperature sensor (channel 16) and the internal reference
voltage (channel 17) in scan mode with DMA circular buffer. A median filter of 9 samples is applied to each
channel.
- **`mcutemp`** returns temperature in tenths of °C, computed as:
`T = 25 + (V_25 - Vsense)/Avg_Slope`, with `V_25=1.45V` and `Avg_Slope=4.3mV/°C`.
- **`vdd`** calculates the supply voltage using the internal 1.2V reference:
`Vdd = 1.2 * 4096 / (ADC reading of Vrefint)`.
These commands do not require a sensor channel.
---
## Examples
### Typical Workflow
1. Connect the device to a USB port and open a terminal at any baud rate.
2. Type `help` to see the available commands.
3. Wake up the sensors and calibrate:
```
wakeup 0
wakeup 1
```
4. Configure gains, thresholds and other values if need:
```
gain 0 = 10
wdthres 1 = 3
```
5. Verify settings:
```
dumpconf
```
6. Enable persistent configuration:
```
restonstart = 1
saveconf
```
7. Monitor lightning events — the device will print `INTERRUPT...` messages automatically. If need, you can
clear statistics periodically or call other setters/getters; for example, reduce the sensitivity or
lightning strike threshold during a severe thunderstorm and restore these values after it ends
### Reading Sensor Status
Any time you can read sensor status (to get noice information or other data):
```
intcode 0
energy 0
distance 0
```
### Saving and Restoring Configuration
```
dumpconf # see current settings
saveconf # write to flash
mcureset # reboot (only if something goes wrong)
```
After reset, the configuration is automatically loaded (if `restonstart` was 1).
---
## How to distinguish between identical device
The device uses standard USB VID/PID, which are shared for free use by ST. Therefore, it may be difficult to
determine which `/dev/ttyACMx` this particular device corresponds to.
You can set custom interface name (`setiface=...`). After storing in flash, reconnect or reset device and
run `lsusb -v` for given device. At `iInterface` field you will see stored name. This field can be used to
create human-readable symlinks in `/dev`.
For automatical creation of symlink in `/dev` to your `/dev/ttyACMx`, add this udev-script to
`/etc/udev/rules.d/99-usbids.rules`
```
ACTION=="add", DRIVERS=="usb", ENV{USB_IDS}="%s{idVendor}:%s{idProduct}"
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"
```
---
## Build Information
Typing `help` you will see project repository URL, build number and build date at first string.
The code is distributed under GPLv3.

View File

@@ -0,0 +1,111 @@
/*
* This file is part of the as3935 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 "adc.h"
uint16_t ADC_array[ADC_CHANNELS*9];
void adc_setup(){
uint32_t ctr = 0;
// Enable clocking
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
RCC->AHBENR |= RCC_AHBENR_DMA1EN;
__DSB();
// DMA configuration
DMA1_Channel1->CPAR = (uint32_t) (&(ADC1->DR));
DMA1_Channel1->CMAR = (uint32_t)(ADC_array);
DMA1_Channel1->CNDTR = ADC_CHANNELS * 9;
DMA1_Channel1->CCR = DMA_CCR_MINC | DMA_CCR_MSIZE_0 | DMA_CCR_PSIZE_0
| DMA_CCR_CIRC | DMA_CCR_PL | DMA_CCR_EN;
RCC->CFGR = (RCC->CFGR & ~(RCC_CFGR_ADCPRE)) | RCC_CFGR_ADCPRE_DIV8; // ADC clock = RCC / 8
// sampling time - 239.5 cycles for channels 0, 16 and 17
ADC1->SMPR2 = ADC_SMPR2_SMP0;
ADC1->SMPR1 = ADC_SMPR1_SMP16 | ADC_SMPR1_SMP17;
// sequence order: 16[tsen] -> 17[vdd]
ADC1->SQR3 = (16 << 0) | (17 << 5);
ADC1->SQR1 = (ADC_CHANNELS - 1) << 20; // amount of conversions
ADC1->CR1 = ADC_CR1_SCAN; // scan mode
// DMA, continuous mode; enable vref & Tsens; enable SWSTART as trigger
ADC1->CR2 = ADC_CR2_DMA | ADC_CR2_TSVREFE | ADC_CR2_CONT | ADC_CR2_EXTSEL | ADC_CR2_EXTTRIG;
// wake up ADC
ADC1->CR2 |= ADC_CR2_ADON;
__DSB();
// wait for Tstab - at least 1us
IWDG->KR = IWDG_REFRESH;
while(++ctr < 0xff) nop();
// calibration
ADC1->CR2 |= ADC_CR2_RSTCAL;
ctr = 0; while((ADC1->CR2 & ADC_CR2_RSTCAL) && ++ctr < 0xfffff) IWDG->KR = IWDG_REFRESH;
ADC1->CR2 |= ADC_CR2_CAL;
ctr = 0; while((ADC1->CR2 & ADC_CR2_CAL) && ++ctr < 0xfffff) IWDG->KR = IWDG_REFRESH;
// clear possible errors and start
ADC1->SR = 0;
ADC1->CR2 |= ADC_CR2_SWSTART;
}
/**
* @brief getADCval - calculate median value for `nch` channel
* @param nch - number of channel
* @return
*/
uint16_t getADCval(int nch){
int i, addr = nch;
#define PIX_SORT(a,b) { if ((a)>(b)) PIX_SWAP((a),(b)); }
#define PIX_SWAP(a,b) { register uint16_t temp=(a);(a)=(b);(b)=temp; }
uint16_t p[9];
for(i = 0; i < 9; ++i, addr += ADC_CHANNELS){ // first we should prepare array for optmed
p[i] = ADC_array[addr];
}
PIX_SORT(p[1], p[2]) ; PIX_SORT(p[4], p[5]) ; PIX_SORT(p[7], p[8]) ;
PIX_SORT(p[0], p[1]) ; PIX_SORT(p[3], p[4]) ; PIX_SORT(p[6], p[7]) ;
PIX_SORT(p[1], p[2]) ; PIX_SORT(p[4], p[5]) ; PIX_SORT(p[7], p[8]) ;
PIX_SORT(p[0], p[3]) ; PIX_SORT(p[5], p[8]) ; PIX_SORT(p[4], p[7]) ;
PIX_SORT(p[3], p[6]) ; PIX_SORT(p[1], p[4]) ; PIX_SORT(p[2], p[5]) ;
PIX_SORT(p[4], p[7]) ; PIX_SORT(p[4], p[2]) ; PIX_SORT(p[6], p[4]) ;
PIX_SORT(p[4], p[2]) ;
return p[4];
#undef PIX_SORT
#undef PIX_SWAP
}
// get voltage @input nch (1/100V)
uint32_t getADCvoltage(int nch){
uint32_t v = getADCval(nch);
v *= getVdd();
v /= 0xfff; // 12bit ADC
return v;
}
// return MCU temperature (degrees of celsius * 10)
int32_t getMCUtemp(){
// Temp = (V25 - Vsense)/Avg_Slope + 25
// V_25 = 1.45V, Slope = 4.3e-3
int32_t Vsense = getVdd() * getADCval(ADC_CH_TSEN);
int32_t temperature = 593920 - Vsense; // 593920 == 145*4096
temperature /= 172; // == /(4096*10*4.3e-3), 10 - to convert from *100 to *10
temperature += 250;
return(temperature);
}
// return Vdd * 100 (V)
uint32_t getVdd(){
uint32_t vdd = 120 * 4096; // 1.2V
vdd /= getADCval(ADC_CH_VDD);
return vdd;
}

View File

@@ -0,0 +1,41 @@
/*
* This file is part of the as3935 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 <stm32f1.h>
// ADC channels in array
enum{
ADC_CH_TSEN, // T sensor
ADC_CH_VDD, // Vdd sensor
ADC_CHANNELS
};
/**
* @brief ADC_array - array for ADC channels with median filtering
*/
extern uint16_t ADC_array[];
void adc_setup();
int32_t getMCUtemp();
uint32_t getVdd();
uint16_t getADCval(int nch);
uint32_t getADCvoltage(int nch);

Binary file not shown.

View File

@@ -0,0 +1,291 @@
/*
* This file is part of the as3935 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 "as3935.h"
#include "spi.h"
#define MODE_READ (1 << 6)
#define MODE_WRITE (0)
#define MODE_MASK (0x3f)
extern uint32_t Tms;
// read one register
int as3935_read(uint8_t reg, uint8_t *data){
uint8_t word[2];
word[0] = MODE_READ | (reg & MODE_MASK);
word[1] = 0;
if(0 == SPI_transmit(word, 2)) return FALSE;
if(data) *data = word[1];
return TRUE;
}
int as3935_write(uint8_t reg, uint8_t data){
uint8_t word[2];
word[0] = MODE_WRITE | (reg & MODE_MASK);
word[1] = data;
if(0 == SPI_transmit(word, 2)) return FALSE;
return TRUE;
}
int as3935_get_displco(uint8_t *n){
t_tun_disp t;
if(!as3935_read(TUN_DISP, &t.u8)) return FALSE; // we need to save old `tun_cap` value
*n = t.DISP_TRCO + 2 * t.DISP_SRCO + 3 * t.DISP_LCO;
return TRUE;
}
// display on IRQ: nothing (0), TRCO (1), SRCO (2) or LCO (3)
int as3935_displco(uint8_t n){
if(n > 3) return FALSE;
t_tun_disp t;
if(!as3935_read(TUN_DISP, &t.u8)) return FALSE; // we need to save old `tun_cap` value
t.DISP_LCO = t.DISP_SRCO = t.DISP_TRCO = 0;
switch(n){
case 1:
t.DISP_TRCO = 1;
break;
case 2:
t.DISP_SRCO = 1;
break;
case 3:
t.DISP_LCO = 1;
break;
default:
break;
}
return as3935_write(TUN_DISP, t.u8);
}
// tune LCO: change capasitor value
int as3935_tuncap(uint8_t n){
if(n > 0xf) return FALSE;
t_tun_disp t;
if(!as3935_read(TUN_DISP, &t.u8)) return FALSE;
t.TUN_CAP = n;
return as3935_write(TUN_DISP, t.u8);
}
int as3935_get_tuncap(uint8_t *n){
t_tun_disp t;
if(!as3935_read(TUN_DISP, &t.u8)) return FALSE;
*n = t.TUN_CAP;
return TRUE;
}
#if 0
// set gain
int as3935_gain(uint8_t n){
if(n > 0x1f) return FALSE;
t_afe_gain g;
if(!as3935_read(AFE_GAIN, &g.u8)) return FALSE;
g.AFE_GB = n;
return as3935_write(AFE_GAIN, g.u8);
}
#endif
// calibrate RCO
int as3935_calib_rco(){
t_tun_disp t;
if(!as3935_read(TUN_DISP, &t.u8)) return FALSE;
if(!as3935_write(CALIB_RCO, DIRECT_COMMAND)) return FALSE;
t.DISP_LCO = t.DISP_TRCO = 0;
t.DISP_SRCO = 1;
if(!as3935_write(TUN_DISP, t.u8)) return FALSE;
uint32_t Tstart = Tms;
while(Tms - Tstart < 3) IWDG->KR = IWDG_REFRESH; // sleep for ~2ms
t.DISP_SRCO = 0;
if(!as3935_write(TUN_DISP, t.u8)) return FALSE;
return TRUE;
}
int as3935_get_calib(uint8_t *n){
t_calib srco, trco;
if(!as3935_read(CALIB_TRCO, &trco.u8)) return FALSE;
if(!as3935_read(CALIB_SRCO, &srco.u8)) return FALSE;
*n = (!srco.CALIB_DONE || !trco.CALIB_DONE) ? 0 : 1;
return TRUE;
}
// wakeup - call this function after first run
int as3935_wakeup(){
t_afe_gain g;
if(!as3935_read(AFE_GAIN, &g.u8)) return FALSE;
g.PWD = 0;
if(!as3935_write(AFE_GAIN, g.u8)) return FALSE;
return TRUE;
}
// set amplifier gain
int as3935_gain(uint8_t g){
if(g > 0x1f) return FALSE;
t_afe_gain a = {0};
a.AFE_GB = g;
return as3935_write(AFE_GAIN, a.u8);
}
int as3935_get_gain(uint8_t *n){
t_afe_gain g;
if(!as3935_read(AFE_GAIN, &g.u8)) return FALSE;
*n = g.AFE_GB;
return TRUE;
}
// watchdog threshold
int as3935_wdthres(uint8_t t){
if(t > 0x0f) return FALSE;
t_threshold thres;
if(!as3935_read(THRESHOLD, &thres.u8)) return FALSE;
thres.WDTH = t;
return as3935_write(THRESHOLD, thres.u8);
}
int as3935_get_wdthres(uint8_t *n){
t_threshold thres;
if(!as3935_read(THRESHOLD, &thres.u8)) return FALSE;
*n = thres.WDTH;
return TRUE;
}
// noice floor level
int as3935_nflev(uint8_t l){
if(l > 7) return FALSE;
t_threshold thres;
if(!as3935_read(THRESHOLD, &thres.u8)) return FALSE;
thres.NF_LEV = l;
return as3935_write(THRESHOLD, thres.u8);
}
int as3935_get_nflev(uint8_t *n){
t_threshold thres;
if(!as3935_read(THRESHOLD, &thres.u8)) return FALSE;
*n = thres.NF_LEV;
return TRUE;
}
// spike rejection
int as3935_srej(uint8_t s){
if(s > 0xf) return FALSE;
t_lightning_reg lr;
if(!as3935_read(LIGHTNING_REG, &lr.u8)) return FALSE;
lr.SREJ = s;
return as3935_write(LIGHTNING_REG, lr.u8);
}
int as3935_get_srej(uint8_t *n){
t_lightning_reg lr;
if(!as3935_read(LIGHTNING_REG, &lr.u8)) return FALSE;
*n = lr.SREJ;
return TRUE;
}
// minimal lighting number
int as3935_minnumlig(uint8_t n){
if(n > 3) return FALSE;
t_lightning_reg lr;
if(!as3935_read(LIGHTNING_REG, &lr.u8)) return FALSE;
lr.MIN_NUM_LIG = n;
return as3935_write(LIGHTNING_REG, lr.u8);
}
int as3935_get_minnumlig(uint8_t *n){
t_lightning_reg lr;
if(!as3935_read(LIGHTNING_REG, &lr.u8)) return FALSE;
*n = lr.MIN_NUM_LIG;
return TRUE;
}
// clear amount of lightnings for last 15 min
int as3935_clearstat(){
t_lightning_reg lr;
if(!as3935_read(LIGHTNING_REG, &lr.u8)) return FALSE;
lr.CL_STAT = 1;
return as3935_write(LIGHTNING_REG, lr.u8);
}
// get interrupt code
int as3935_intcode(uint8_t *code){
if(!code) return FALSE;
t_int_mask_ant i;
if(!as3935_read(INT_MASK_ANT, &i.u8)) return FALSE;
*code = i.INT;
return TRUE;
}
// should interrupt react on disturbers?
int as3935_maskdist(uint8_t m){
if(m > 1) return FALSE;
t_int_mask_ant i;
if(!as3935_read(INT_MASK_ANT, &i.u8)) return FALSE;
i.MASK_DIST = m;
return as3935_write(INT_MASK_ANT, i.u8);
}
int as3935_get_maskdist(uint8_t *n){
t_int_mask_ant i;
if(!as3935_read(INT_MASK_ANT, &i.u8)) return FALSE;
*n = i.MASK_DIST;
return TRUE;
}
// set Fdiv of antenna LCO
int as3935_lco_fdiv(uint8_t d){
if(d > 3) return FALSE;
t_int_mask_ant i;
if(!as3935_read(INT_MASK_ANT, &i.u8)) return FALSE;
i.LCO_FDIV = d;
return as3935_write(INT_MASK_ANT, i.u8);
}
int as3935_get_lco_fdiv(uint8_t *n){
t_int_mask_ant i;
if(!as3935_read(INT_MASK_ANT, &i.u8)) return FALSE;
*n = i.LCO_FDIV;
return TRUE;
}
// calculate last lightning energy
int as3935_energy(uint32_t *E){
if(!E) return FALSE;
uint8_t u; uint32_t energy;
t_s_lig_mm mm;
if(!as3935_read(S_LIG_MM, &mm.u8)) return FALSE;
energy = mm.S_LIG_MM << 8;
if(!as3935_read(S_LIG_M, &u)) return FALSE;
energy |= u;
energy <<= 8;
if(!as3935_read(S_LIG_L, &u)) return FALSE;
energy |= u;
*E = energy;
return TRUE;
}
// get distance
int as3935_distance(uint8_t *d){
if(!d) return FALSE;
t_distance dist;
if(!as3935_read(DISTANCE, &dist.u8)) return FALSE;
*d = dist.DISTANCE;
return TRUE;
}
// reset to factory settings
int as3935_resetdef(){
return as3935_write(PRESET_DEFAULT, DIRECT_COMMAND);
}

View File

@@ -0,0 +1 @@
-std=c17

View File

@@ -0,0 +1,6 @@
#define EBUG
#define STM32F1
#define STM32F103x8
#define STM32F10X_MD
#define BUILD_NUMBER 1
#define BUILD_DATE "xx"

View File

@@ -0,0 +1 @@
[General]

View File

@@ -0,0 +1,219 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 19.0.1, 2026-04-25T20:44:46. -->
<qtcreator>
<data>
<variable>EnvironmentId</variable>
<value type="QByteArray">{7bd84e39-ca37-46d3-be9d-99ebea85bc0d}</value>
</data>
<data>
<variable>ProjectExplorer.Project.ActiveTarget</variable>
<value type="qlonglong">0</value>
</data>
<data>
<variable>ProjectExplorer.Project.EditorSettings</variable>
<valuemap type="QVariantMap">
<value type="bool" key="EditorConfiguration.AutoDetect">true</value>
<value type="bool" key="EditorConfiguration.AutoIndent">true</value>
<value type="bool" key="EditorConfiguration.CamelCaseNavigation">true</value>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.0">
<value type="QString" key="language">Cpp</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">CppGlobal</value>
</valuemap>
</valuemap>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.1">
<value type="QString" key="language">QmlJS</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">QmlJSGlobal</value>
</valuemap>
</valuemap>
<value type="qlonglong" key="EditorConfiguration.CodeStyle.Count">2</value>
<value type="QByteArray" key="EditorConfiguration.Codec">KOI8-R</value>
<value type="bool" key="EditorConfiguration.ConstrainTooltips">false</value>
<value type="int" key="EditorConfiguration.IndentSize">4</value>
<value type="bool" key="EditorConfiguration.KeyboardTooltips">false</value>
<value type="int" key="EditorConfiguration.LineEndingBehavior">0</value>
<value type="int" key="EditorConfiguration.MarginColumn">80</value>
<value type="bool" key="EditorConfiguration.MouseHiding">true</value>
<value type="bool" key="EditorConfiguration.MouseNavigation">true</value>
<value type="int" key="EditorConfiguration.PaddingMode">1</value>
<value type="int" key="EditorConfiguration.PreferAfterWhitespaceComments">0</value>
<value type="bool" key="EditorConfiguration.PreferSingleLineComments">false</value>
<value type="bool" key="EditorConfiguration.ScrollWheelZooming">true</value>
<value type="bool" key="EditorConfiguration.ShowMargin">false</value>
<value type="int" key="EditorConfiguration.SmartBackspaceBehavior">0</value>
<value type="bool" key="EditorConfiguration.SmartSelectionChanging">true</value>
<value type="bool" key="EditorConfiguration.SpacesForTabs">true</value>
<value type="int" key="EditorConfiguration.TabKeyBehavior">0</value>
<value type="int" key="EditorConfiguration.TabSize">8</value>
<value type="bool" key="EditorConfiguration.UseGlobal">true</value>
<value type="bool" key="EditorConfiguration.UseIndenter">false</value>
<value type="int" key="EditorConfiguration.Utf8BomBehavior">1</value>
<value type="bool" key="EditorConfiguration.addFinalNewLine">true</value>
<value type="bool" key="EditorConfiguration.cleanIndentation">false</value>
<value type="bool" key="EditorConfiguration.cleanWhitespace">true</value>
<value type="QString" key="EditorConfiguration.ignoreFileTypes">*.md, *.MD, Makefile</value>
<value type="bool" key="EditorConfiguration.inEntireDocument">false</value>
<value type="bool" key="EditorConfiguration.skipTrailingWhitespace">true</value>
<value type="bool" key="EditorConfiguration.tintMarginArea">true</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.PluginSettings</variable>
<valuemap type="QVariantMap">
<valuemap type="QVariantMap" key="AutoTest.ActiveFrameworks">
<value type="bool" key="AutoTest.Framework.Boost">true</value>
<value type="bool" key="AutoTest.Framework.CTest">false</value>
<value type="bool" key="AutoTest.Framework.Catch">true</value>
<value type="bool" key="AutoTest.Framework.GTest">true</value>
<value type="bool" key="AutoTest.Framework.QtQuickTest">true</value>
<value type="bool" key="AutoTest.Framework.QtTest">true</value>
</valuemap>
<value type="bool" key="AutoTest.ApplyFilter">false</value>
<valuemap type="QVariantMap" key="AutoTest.CheckStates"/>
<valuelist type="QVariantList" key="AutoTest.PathFilters"/>
<value type="int" key="AutoTest.RunAfterBuild">0</value>
<value type="bool" key="AutoTest.UseGlobal">true</value>
<valuemap type="QVariantMap" key="ClangTools">
<value type="bool" key="ClangTools.AnalyzeOpenFiles">true</value>
<value type="bool" key="ClangTools.BuildBeforeAnalysis">true</value>
<value type="QString" key="ClangTools.DiagnosticConfig">Builtin.DefaultTidyAndClazy</value>
<value type="int" key="ClangTools.ParallelJobs">8</value>
<value type="bool" key="ClangTools.PreferConfigFile">true</value>
<valuelist type="QVariantList" key="ClangTools.SelectedDirs"/>
<valuelist type="QVariantList" key="ClangTools.SelectedFiles"/>
<valuelist type="QVariantList" key="ClangTools.SuppressedDiagnostics"/>
<value type="bool" key="ClangTools.UseGlobalSettings">true</value>
</valuemap>
<value type="int" key="RcSync">0</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.Target.0</variable>
<valuemap type="QVariantMap">
<value type="QString" key="DeviceType">Desktop</value>
<value type="bool" key="HasPerBcDcs">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{65a14f9e-e008-4c1b-89df-4eaa4774b6e3}</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveBuildConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<valuelist type="QVariantList" key="GenericProjectManager.GenericMakeStep.BuildTargets">
<value type="QString">all</value>
</valuelist>
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericMakeStep</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<valuelist type="QVariantList" key="GenericProjectManager.GenericMakeStep.BuildTargets">
<value type="QString">clean</value>
</valuelist>
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericMakeStep</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.CustomParsers"/>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ParseStandardOutput">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Default</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericBuildConfiguration</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
<value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
<value type="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
<valuelist type="QVariantList" key="Analyzer.Valgrind.SuppressionFiles"/>
<value type="QList&lt;int&gt;" key="Analyzer.Valgrind.VisibleErrorKinds"></value>
<valuelist type="QVariantList" key="CustomOutputParsers"/>
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
<value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
<value type="QString" key="PerfRecordArgsId">-e cpu-cycles --call-graph dwarf,4096 -F 250</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.BuildConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
<value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
<value type="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
<valuelist type="QVariantList" key="Analyzer.Valgrind.SuppressionFiles"/>
<value type="QList&lt;int&gt;" key="Analyzer.Valgrind.VisibleErrorKinds"></value>
<valuelist type="QVariantList" key="CustomOutputParsers"/>
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
<value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
<value type="QString" key="PerfRecordArgsId">-e cpu-cycles --call-graph dwarf,4096 -F 250</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.TargetCount</variable>
<value type="qlonglong">1</value>
</data>
<data>
<variable>Version</variable>
<value type="int">22</value>
</data>
</qtcreator>

View File

@@ -0,0 +1 @@
-std=c++17

View File

@@ -0,0 +1,28 @@
Readme.md
adc.c
adc.h
as3935.c
as3935.h
bissC.c
bissC.h
commproto.cpp
commproto.h
flash.c
flash.h
hardware.c
hardware.h
main.c
ringbuffer.c
ringbuffer.h
spi.c
spi.h
strfunc.c
strfunc.h
usart.c
usart.h
usb_descr.c
usb_descr.h
usb_lib.c
usb_lib.h
usb_dev.c
usb_dev.h

View File

@@ -0,0 +1,153 @@
/*
* This file is part of the as3935 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>
enum AS3935_REGISTERS{
AFE_GAIN = 0x00,
THRESHOLD,
LIGHTNING_REG,
INT_MASK_ANT,
S_LIG_L,
S_LIG_M,
S_LIG_MM,
DISTANCE,
TUN_DISP,
CALIB_TRCO = 0x3A,
CALIB_SRCO = 0x3B,
PRESET_DEFAULT = 0x3C,
CALIB_RCO = 0x3D
};
// REGISTERS
typedef union{
struct{
uint8_t PWD : 1;
uint8_t AFE_GB : 5;
uint8_t RESERVED : 2;
}; uint8_t u8;
} t_afe_gain;
typedef union{
struct{
uint8_t WDTH : 4;
uint8_t NF_LEV : 3;
uint8_t RESERVED : 1;
}; uint8_t u8;
} t_threshold;
typedef union{
struct{
uint8_t SREJ : 4;
// minimal number of lightnings
#define NUM_LIG_1 (0)
#define NUM_LIG_5 (1)
#define NUM_LIG_9 (2)
#define NUM_LIG_16 (3)
uint8_t MIN_NUM_LIG : 2;
uint8_t CL_STAT : 1;
uint8_t RESERVED : 1;
}; uint8_t u8;
} t_lightning_reg;
typedef union{
struct{
// interrupt flags
// noice level too high
#define INT_NH (1)
// disturber detected
#define INT_D (4)
// lightning interrupt
#define INT_L (8)
uint8_t INT : 4;
uint8_t RESERVED : 1;
uint8_t MASK_DIST : 1;
uint8_t LCO_FDIV : 2;
}; uint8_t u8;
} t_int_mask_ant;
typedef union{
struct{
uint8_t S_LIG_MM : 5;
uint8_t RESERVED : 3;
}; uint8_t u8;
} t_s_lig_mm;
typedef union{
struct{
uint8_t DISTANCE : 6;
uint8_t RESERVED : 2;
}; uint8_t u8;
} t_distance;
typedef union{
struct{
uint8_t TUN_CAP : 4;
uint8_t RESERVED : 1;
uint8_t DISP_TRCO : 1;
uint8_t DISP_SRCO : 1;
uint8_t DISP_LCO : 1;
}; uint8_t u8;
} t_tun_disp;
typedef union{
struct{
uint8_t RESERVED : 6;
uint8_t CALIB_NOK : 1;
uint8_t CALIB_DONE : 1;
}; uint8_t u8;
} t_calib;
// direct command send to PRESET_DEFAULT and CALIB_RCO
#define DIRECT_COMMAND (0x96)
// distance out of range
#define DIST_OUT_OF_RANGE (0x3f)
int as3935_read(uint8_t reg, uint8_t *data);
int as3935_write(uint8_t reg, uint8_t data);
int as3935_wakeup();
int as3935_get_calib(uint8_t *n);
int as3935_calib_rco();
int as3935_displco(uint8_t n);
int as3935_get_displco(uint8_t *n);
int as3935_tuncap(uint8_t n);
int as3935_get_tuncap(uint8_t *n);
int as3935_gain(uint8_t n);
int as3935_get_gain(uint8_t *n);
//int as3935_set_gain(uint8_t g);
int as3935_wdthres(uint8_t t);
int as3935_get_wdthres(uint8_t *t);
int as3935_nflev(uint8_t l);
int as3935_get_nflev(uint8_t *l);
int as3935_srej(uint8_t s);
int as3935_get_srej(uint8_t *s);
int as3935_minnumlig(uint8_t n);
int as3935_get_minnumlig(uint8_t *n);
int as3935_clearstat();
int as3935_intcode(uint8_t *code);
int as3935_maskdist(uint8_t m);
int as3935_get_maskdist(uint8_t *m);
int as3935_lco_fdiv(uint8_t d);
int as3935_get_lco_fdiv(uint8_t *d);
int as3935_energy(uint32_t *E);
int as3935_distance(uint8_t *d);
int as3935_resetdef();

View File

@@ -0,0 +1,6 @@
.
../inc
../inc/Fx
../inc/cm
../inc/ld
../inc/startup

View File

@@ -0,0 +1,467 @@
/*
* This file is part of the as3935 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 <cstring>
extern "C"{
#include <stm32f1.h>
#include "adc.h"
#include "as3935.h"
#include "commproto.h"
#include "flash.h"
#include "hardware.h"
#include "spi.h"
#include "strfunc.h"
}
// sending function
static int (*SEND)(const char *str) = nullptr;
extern volatile uint32_t Tms;
static uint8_t curbuf[MAXSTRLEN];
// COMMAND(USART, "Read USART data or send (USART=hex)")
// list of all commands and handlers
#define COMMAND_TABLE \
COMMAND(clearstat, "clear amount of lightnings for last 15 min") \
COMMAND(displco, "display on LCO: 0- nothing, 1- TRCO, 2- SRCO, 3- LCO") \
COMMAND(distance, "distance to lightning, km") \
COMMAND(dumpconf, "dump current configuration") \
COMMAND(energy, "energy of last lightning") \
COMMAND(eraseflash, "erase full flash storage") \
COMMAND(gain, "change sensor's gain (0..1f)") \
COMMAND(help, "show this help") \
COMMAND(intcode, "last interrupt code") \
COMMAND(iscalib, "check if sensor x calibrated") \
COMMAND(lco_fdiv, "set Fdiv of antenna LCO") \
COMMAND(maskdist, "mask (1) or unmask (0) disturber") \
COMMAND(mcutemp, "get MCU temperature (degC*10)") \
COMMAND(mcureset, "reset MCU") \
COMMAND(minnumlig, "ninimal lightnings number (0..3 -> 1/5/9/16)") \
COMMAND(nflev, "noice floor level (0..7)") \
COMMAND(readconf, "read configuration from given sensor") \
COMMAND(resetdef, "reset sensor to defaults") \
COMMAND(restonstart,"restore sensors configuration on start") \
COMMAND(saveconf, "save current configuration into flash") \
COMMAND(setiface, "set USB interface name (max 16 symbols)") \
COMMAND(SPI, "transfer SPI data: SPIx=data (hex)") \
COMMAND(srej, "spike rejection (0..15)") \
COMMAND(time, "show current time (ms)") \
COMMAND(tuncap, "set 'tune capasitors' to given value (0..15)") \
COMMAND(vdd, "get approx Vdd value (V*100)") \
COMMAND(wakeup, "wake-up given sensor and make its calibration") \
COMMAND(wdthres, "watchdog threshold (0..15)" )
typedef struct {
const char *name;
const char *desc;
} CmdInfo;
// prototypes
#define COMMAND(name, desc) static errcodes_t cmd_ ## name(const char*, char*);
COMMAND_TABLE
#undef COMMAND
static const CmdInfo cmdInfo[] = { // command name, description - for `help`
#define COMMAND(name, desc) { #name, desc },
COMMAND_TABLE
#undef COMMAND
};
static const char* errtxt[ERR_AMOUNT] = {
[ERR_OK] = "OK\n",
[ERR_BADCMD] = "BADCMD\n",
[ERR_BADPAR] = "BADPAR\n",
[ERR_BADVAL] = "BADVAL\n",
[ERR_WRONGLEN] = "WRONGLEN\n",
[ERR_CANTRUN] = "CANTRUN\n",
[ERR_BUSY] = "BUSY\n",
[ERR_OVERFLOW] = "OVERFLOW\n",
};
const char *EQ = " = "; // equal sign for getters
// send `command = `
#define CMDEQ() do{SEND(cmd); SEND(EQ);}while(0)
// send `commandXXX = `
#define CMDEQP(x) do{SEND(cmd); SEND(u2str((uint32_t)x)); SEND(EQ);}while(0)
/**
* @brief splitargs - get command parameter and setter from `args`
* @param args (i) - rest of string after command (like `1 = PU OD OUT`)
* @param parno (o) - parameter number or -1 if none
* @return setter (part after `=` without leading spaces) or NULL if none
*/
static char *splitargs(char *args, int32_t *parno){
if(!args) return NULL;
uint32_t U32;
char *next = getnum(args, &U32);
int p = -1;
if(next != args && U32 <= MAXPARNO) p = U32;
if(parno) *parno = p;
next = strchr(next, '=');
if(next){
if(*(++next)) next = omit_spaces(next);
if(*next == 0) next = NULL;
}
return next;
}
/**
* @brief argsvals - split `args` into `parno` and setter's value
* @param args - rest of string after command
* @param parno (o) - parameter number or -1 if none
* @param parval - integer setter's value
* @return false if no setter or it's not a number, true - got setter's num
*/
static bool argsvals(char *args, int32_t *parno, int32_t *parval){
char *setter = splitargs(args, parno);
if(!setter) return false;
int32_t I32;
char *next = getint(setter, &I32);
if(next != setter && parval){
*parval = I32;
return true;
}
return false;
}
static errcodes_t cmd_time(const char *cmd, char*){
CMDEQ();
SEND(u2str(Tms)); SEND("\n");
return ERR_AMOUNT;
}
static errcodes_t cmd_mcureset(const char*, char*){
NVIC_SystemReset();
return ERR_CANTRUN; // never reached
}
static errcodes_t cmd_saveconf(const char*, char*){
if(store_userconf()) return ERR_CANTRUN;
return ERR_OK;
}
static errcodes_t cmd_eraseflash(const char*, char*){
if(erase_storage(-1)) return ERR_CANTRUN;
return ERR_OK;
}
static errcodes_t cmd_readconf(const char*, char* args){
int32_t CHno = -1;
splitargs(args, &CHno);
if(CHno < 0 || CHno >= SENSORS_AMOUNT) return ERR_BADPAR;
as3935_channel = static_cast<uint8_t>(CHno);
uint8_t par;
if(!as3935_get_gain(&par)) return ERR_CANTRUN;
the_conf.spars[CHno].AFE_GB = par;
if(!as3935_get_wdthres(&par)) return ERR_CANTRUN;
the_conf.spars[CHno].WDTH = par;
if(!as3935_get_nflev(&par)) return ERR_CANTRUN;
the_conf.spars[CHno].NF_LEV = par;
if(!as3935_get_srej(&par)) return ERR_CANTRUN;
the_conf.spars[CHno].SREJ = par;
if(!as3935_get_minnumlig(&par)) return ERR_CANTRUN;
the_conf.spars[CHno].MIN_NUM_LIG = par;
if(!as3935_get_maskdist(&par)) return ERR_CANTRUN;
the_conf.spars[CHno].MASK_DIST = par;
if(!as3935_get_lco_fdiv(&par)) return ERR_CANTRUN;
the_conf.spars[CHno].LCO_FDIV = par;
if(!as3935_get_tuncap(&par)) return ERR_CANTRUN;
the_conf.spars[CHno].TUN_CAP = par;
return ERR_OK;
}
static errcodes_t cmd_setiface(const char* cmd, char* args){
char *ifname = splitargs(args, NULL);
if(ifname){ // setter
int l = strlen(ifname);
if(l > MAX_IINTERFACE_SZ) return ERR_BADPAR; // too long
the_conf.iIlength = (uint8_t) l * 2;
char *ptr = (char*)the_conf.iInterface;
for(int i = 0; i < l; ++i){
*ptr++ = *ifname++;
*ptr++ = 0;
}
}
// getter
CMDEQ();
int l = the_conf.iIlength / 2;
char *ptr = (char*)the_conf.iInterface;
for(int i = 0; i < l; ++i){
SEND(ptr); // next is always zero
ptr += 2;
}
SEND("\n");
return ERR_AMOUNT;
}
static errcodes_t cmd_restonstart(const char* cmd, char* args){
int32_t val;
if(argsvals(args, NULL, &val)){ // setter
if(!val) the_conf.flags.restore = 0;
else the_conf.flags.restore = 1;
}
CMDEQ();
SEND(u2str(the_conf.flags.restore));
SEND("\n");
return ERR_AMOUNT;
}
static void showpar(const char *par, uint8_t n, uint8_t v){
char c[2];
c[0] = '0' + n; c[1] = 0;
SEND(par); SEND(c); SEND(EQ);
SEND(u2str(v));
}
static errcodes_t cmd_dumpconf(const char*, char*){
SEND("userconf_sz="); SEND(u2str(the_conf.userconf_sz));
SEND("\ncurr_idx="); SEND(u2str(currentconfidx));
SEND("\ncapacity="); SEND(u2str(maxCnum-2));
cmd_setiface("\nsetiface", NULL);
cmd_restonstart("restonstart", NULL);
for(int i = 0; i < SENSORS_AMOUNT; ++i){
showpar("gain", i, the_conf.spars[i].AFE_GB);
showpar("\nlco_fdiv", i, the_conf.spars[i].LCO_FDIV);
showpar("\nmaskdist", i, the_conf.spars[i].MASK_DIST);
showpar("\nminnumlig", i, the_conf.spars[i].MIN_NUM_LIG);
showpar("\nnflev", i, the_conf.spars[i].NF_LEV);
showpar("\nsrej", i, the_conf.spars[i].SREJ);
showpar("\ntuncap", i, the_conf.spars[i].TUN_CAP);
showpar("\nwdthres", i, the_conf.spars[i].WDTH);
SEND("\n");
}
return ERR_AMOUNT;
}
static errcodes_t cmd_mcutemp(const char *cmd, char*){
CMDEQ();
SEND(i2str(getMCUtemp())); SEND("\n");
return ERR_AMOUNT;
}
static errcodes_t cmd_vdd(const char *cmd, char*){
CMDEQ();
SEND(u2str(getVdd())); SEND("\n");
return ERR_AMOUNT;
}
static errcodes_t cmd_help(const char*, char*){
SEND(REPOURL);
for(size_t i = 0; i < sizeof(cmdInfo)/sizeof(cmdInfo[0]); i++){
SEND(cmdInfo[i].name);
SEND(" - ");
SEND(cmdInfo[i].desc); SEND("\n");
}
return ERR_AMOUNT;
}
static errcodes_t senscmd8(int32_t CHno, int (*cmd)(uint8_t), int32_t arg){
if(CHno < 0 || CHno >= SENSORS_AMOUNT) return ERR_BADPAR;
uint8_t par = static_cast<uint8_t>(arg);
as3935_channel = static_cast<uint8_t>(CHno);
int ans = cmd(par);
if(ans) return ERR_OK;
return ERR_CANTRUN;
}
static errcodes_t getta(const char *cmd, int32_t CHno, int (*fn)(uint8_t*)){
if(CHno < 0 || CHno >= SENSORS_AMOUNT) return ERR_BADPAR;
uint8_t par;
as3935_channel = static_cast<uint8_t>(CHno);
int ans = fn(&par);
if(!ans) return ERR_CANTRUN;
CMDEQP(CHno); SEND(u2str(par)); SEND("\n");
return ERR_AMOUNT;
}
static errcodes_t senscmd(int32_t CHno, int (*cmd)()){
if(CHno < 0 || CHno >= SENSORS_AMOUNT) return ERR_BADPAR;
as3935_channel = static_cast<uint8_t>(CHno);
int ans = cmd();
if(ans) return ERR_OK;
return ERR_CANTRUN;
}
static errcodes_t cmd_displco(const char *cmd, char *args){
int32_t CHno, val;
if(!argsvals(args, &CHno, &val)) return getta(cmd, CHno, as3935_get_displco);
errcodes_t ret = senscmd8(CHno, as3935_displco, val);
if(ret == ERR_OK) DISPLCO[CHno] = val;
return ret;
}
#define AS3935_FN8(nm) \
static errcodes_t cmd_ ## nm(const char* cmd, char* args){ \
int32_t CHno = -1, val; \
if(!argsvals(args, &CHno, &val)) return getta(cmd, CHno, as3935_get_ ## nm); \
return senscmd8(CHno, as3935_ ## nm, val);}
#define AS3935_FN(nm) \
static errcodes_t cmd_ ## nm(const char*, char* args){ \
int32_t CHno = -1; \
splitargs(args, &CHno); \
return senscmd(CHno, as3935_ ## nm);}
AS3935_FN8(tuncap)
AS3935_FN8(gain)
AS3935_FN8(wdthres)
AS3935_FN8(nflev)
AS3935_FN8(srej)
AS3935_FN8(minnumlig)
AS3935_FN8(maskdist)
AS3935_FN8(lco_fdiv)
AS3935_FN(clearstat)
AS3935_FN(resetdef)
#define AS3935_GU8(nm) \
static errcodes_t cmd_ ## nm(const char* cmd, char* args){ \
int32_t CHno = -1; uint8_t par;\
splitargs(args, &CHno); \
if(CHno < 0 || CHno >= SENSORS_AMOUNT) return ERR_BADPAR; \
as3935_channel = static_cast<uint8_t>(CHno); \
int ans = as3935_ ## nm(&par); \
if(!ans) return ERR_CANTRUN; \
CMDEQP(CHno); SEND(u2str(par)); SEND("\n"); \
return ERR_AMOUNT; }
AS3935_GU8(intcode)
AS3935_GU8(distance)
static errcodes_t cmd_iscalib(const char* cmd, char* args){
int32_t CHno = -1;
splitargs(args, &CHno);
return getta(cmd, CHno, as3935_get_calib);
}
static errcodes_t cmd_wakeup(const char*, char* args){
int32_t CHno = -1;
splitargs(args, &CHno);
if(CHno < 0 || CHno >= SENSORS_AMOUNT) return ERR_BADPAR;
as3935_channel = static_cast<uint8_t>(CHno);
if(!as3935_wakeup() || !as3935_calib_rco()) return ERR_CANTRUN;
return ERR_OK;
}
static errcodes_t cmd_energy(const char* cmd, char* args){
int32_t CHno = -1; uint32_t par;
splitargs(args, &CHno);
if(CHno < 0 || CHno >= SENSORS_AMOUNT) return ERR_BADPAR;
as3935_channel = static_cast<uint8_t>(CHno);
if(!as3935_energy(&par)) return ERR_CANTRUN;
CMDEQP(CHno); SEND(u2str(par)); SEND("\n");
return ERR_AMOUNT;
}
/**
* @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;
}
static errcodes_t cmd_SPI(const char* cmd, char *args){
if(!args) return ERR_BADVAL;
if(!(SPI1->CR1 & SPI_CR1_SPE)) return ERR_CANTRUN;
int32_t CHno;
char *setter = splitargs(args, &CHno);
if(!setter) return ERR_BADVAL;
if(CHno < 0 || CHno >= SENSORS_AMOUNT) return ERR_BADPAR;
int len = parse_hex_data(setter, curbuf, MAXSTRLEN);
if(len <= 0) return ERR_BADVAL;
as3935_channel = static_cast<uint8_t>(CHno);
uint8_t ret = SPI_transmit(curbuf, len);
if(!ret) return ERR_BUSY;
CMDEQP(CHno);
if(len > 8) SEND("\n");
hexdump(SEND, curbuf, len);
return ERR_AMOUNT;
}
constexpr uint32_t hash(const char* str, uint32_t h = 0){
return *str ? hash(str + 1, h + ((h << 7) ^ *str)) : h;
}
const char *parse_cmd(int (*sendfun)(const char *), char *str){
SEND = sendfun;
char command[CMD_MAXLEN+1];
int i = 0;
while(*str > '@' && i < CMD_MAXLEN){ command[i++] = *str++; }
command[i] = 0;
while(*str && *str <= ' ') ++str;
char *restof = (char*) str;
uint32_t h = hash(command);
errcodes_t ecode = ERR_AMOUNT;
switch(h){
#define COMMAND(name, desc) case hash(#name): ecode = cmd_ ## name(command, restof); break;
COMMAND_TABLE
#undef COMMAND
default: SEND("Unknown command, try 'help'\n"); break;
}
if(ecode < ERR_AMOUNT) return errtxt[ecode];
return NULL;
}
// show lightning information
void lightning_info(int (*sendfun)(const char*), uint8_t CHno){
if(!sendfun || CHno >= SENSORS_AMOUNT) return;
char c[2];
c[0] = '0' + CHno; c[1] = 0;
SEND = sendfun;
cmd_energy("energy", c);
cmd_distance("distance", c);
}

View File

@@ -0,0 +1,54 @@
/*
* This file is part of the as3935 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 "version.inc"
#ifdef EBUG
#define RLSDBG "debug"
#else
#define RLSDBG "release"
#endif
#define REPOURL "https://github.com/eddyem/stm32samples/tree/master/F1:F103/AS3935-lightning " RLSDBG " build #" BUILD_NUMBER "@" BUILD_DATE "\n"
// error codes for answer message
typedef enum{
ERR_OK, // all OK
ERR_BADCMD, // wrong command
ERR_BADPAR, // wrong parameter
ERR_BADVAL, // wrong value (for setter)
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;
// maximal length of command (without trailing zero)
#define CMD_MAXLEN 15
// maximal available parameter number
#define MAXPARNO 255
// maximal string length includint terminating zero
#define MAXSTRLEN 256
extern const char *EQ;
const char *parse_cmd(int (*sendfun)(const char*), char *buf);
void lightning_info(int (*sendfun)(const char*), uint8_t CHno);

View File

@@ -0,0 +1,169 @@
/*
* This file is part of the as3935 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 <stm32f1.h>
#include <string.h> // memcpy
#include "flash.h"
#include "strfunc.h"
extern const uint32_t _BLOCKSIZE;
extern const user_conf __varsstart;
static const uint32_t FLASH_blocksize = (uint32_t)&_BLOCKSIZE;
uint32_t maxCnum = 1024 / sizeof(user_conf); // can't use blocksize here
static int write2flash(const void*, const void*, uint32_t);
const user_conf *Flash_Data = (const user_conf *)(&__varsstart);
user_conf the_conf = {
.userconf_sz = sizeof(user_conf),
};
int currentconfidx = -1; // index of current configuration
/**
* @brief binarySearch - binary search in flash for last non-empty cell
* any struct searched should have its sizeof() @ the first field!!!
* @param l - left index
* @param r - right index (should be @1 less than last index!)
* @param start - starting address
* @param stor_size - size of structure to search
* @return index of non-empty cell or -1
*/
static int binarySearch(int r, const uint8_t *start, int stor_size){
int l = 0;
while(r >= l){
int mid = l + (r - l) / 2;
const uint8_t *s = start + mid * stor_size;
if(*((const uint16_t*)s) == stor_size){
if(*((const uint16_t*)(s + stor_size)) == 0xffff){ // next is free
return mid;
}else{ // element is to the right
l = mid + 1;
}
}else{ // element is to the left
r = mid - 1;
}
}
return -1; // not found
}
/**
* @brief flashstorage_init - initialization of user conf storage
* run in once @ start
*/
void flashstorage_init(){
if(FLASH_SIZE > 0 && FLASH_SIZE < 20000){
uint32_t flsz = FLASH_SIZE * 1024; // size in bytes
flsz -= (uint32_t)(&__varsstart) - FLASH_BASE;
maxCnum = flsz / sizeof(user_conf);
}
// -1 if there's no data at all & flash is clear; maxnum-1 if flash is full
currentconfidx = binarySearch((int)maxCnum-2, (const uint8_t*)Flash_Data, sizeof(user_conf));
if(currentconfidx > -1){
memcpy(&the_conf, &Flash_Data[currentconfidx], sizeof(user_conf));
}
}
// store new configuration
// @return 0 if all OK
int store_userconf(){
// maxnum - 3 means that there always should be at least one empty record after last data
// for binarySearch() checking that there's nothing more after it!
if(currentconfidx > (int)maxCnum - 3){ // there's no more place
currentconfidx = 0;
if(erase_storage(-1)) return 1;
}else ++currentconfidx; // take next data position (0 - within first run after firmware flashing)
return write2flash((const void*)&Flash_Data[currentconfidx], &the_conf, sizeof(the_conf));
}
static int write2flash(const void *start, const void *wrdata, uint32_t stor_size){
int ret = 0;
if (FLASH->CR & FLASH_CR_LOCK){ // unloch flash
FLASH->KEYR = FLASH_KEY1;
FLASH->KEYR = FLASH_KEY2;
}
while (FLASH->SR & FLASH_SR_BSY) IWDG->KR = IWDG_REFRESH;
FLASH->SR = FLASH_SR_EOP | FLASH_SR_PGERR | FLASH_SR_WRPRTERR; // clear all flags
FLASH->CR |= FLASH_CR_PG;
const uint16_t *data = (const uint16_t*) wrdata;
volatile uint16_t *address = (volatile uint16_t*) start;
uint32_t i, count = (stor_size + 1) / 2;
for(i = 0; i < count; ++i){
*(volatile uint16_t*)(address + i) = data[i];
while(FLASH->SR & FLASH_SR_BSY) IWDG->KR = IWDG_REFRESH;
if(*(volatile uint16_t*)(address + i) != data[i]){
ret = 1;
break;
}
if(FLASH->SR & FLASH_SR_PGERR){
ret = 1; // program error - meet not 0xffff
break;
}
FLASH->SR = FLASH_SR_EOP | FLASH_SR_PGERR | FLASH_SR_WRPRTERR;
}
FLASH->CR &= ~(FLASH_CR_PG);
return ret;
}
// erase Nth page of flash storage (flash should be prepared!)
static int erase_pageN(int N){
int ret = 0;
FLASH->AR = (uint32_t)Flash_Data + N*FLASH_blocksize;
FLASH->CR |= FLASH_CR_STRT;
while(FLASH->SR & FLASH_SR_BSY) IWDG->KR = IWDG_REFRESH;
FLASH->SR = FLASH_SR_EOP;
if(FLASH->SR & FLASH_SR_WRPRTERR){ /* Check Write protection error */
ret = 1;
FLASH->SR = FLASH_SR_WRPRTERR; /* Clear the flag by software by writing it at 1*/
}
return ret;
}
// erase full storage (npage < 0) or its nth page; @return 0 if all OK
int erase_storage(int npage){
int ret = 0;
uint32_t end = 1, start = 0, flsz = 0;
if(FLASH_SIZE > 0 && FLASH_SIZE < 20000){
flsz = FLASH_SIZE * 1024; // size in bytes
flsz -= (uint32_t)Flash_Data - FLASH_BASE;
}
end = flsz / FLASH_blocksize;
if(end == 0 || end >= FLASH_SIZE) return 1;
if(npage > -1){ // erase only one page
if((uint32_t)npage >= end) return 1;
start = npage;
end = start + 1;
}
if((FLASH->CR & FLASH_CR_LOCK) != 0){
FLASH->KEYR = FLASH_KEY1;
FLASH->KEYR = FLASH_KEY2;
}
while(FLASH->SR & FLASH_SR_BSY) IWDG->KR = IWDG_REFRESH;
FLASH->SR = FLASH_SR_EOP | FLASH_SR_PGERR | FLASH_SR_WRPRTERR;
FLASH->CR |= FLASH_CR_PER;
for(uint32_t i = start; i < end; ++i){
if(erase_pageN(i)){
ret = 1;
break;
}
}
FLASH->CR &= ~FLASH_CR_PER;
return ret;
}

View File

@@ -0,0 +1,67 @@
/*
* This file is part of the as3935 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>
#include "hardware.h" // for SENSORS_AMOUNT
#define FLASH_SIZE_REG ((uint32_t)0x1FFFF7E0)
#define FLASH_SIZE *((uint16_t*)FLASH_SIZE_REG)
// maximal size (in letters) of iInterface for settings
#define MAX_IINTERFACE_SZ (16)
typedef struct{
// t_afe_gain
uint8_t AFE_GB : 5;
// t_threshold
uint8_t WDTH : 4;
uint8_t NF_LEV : 3;
// t_lightning_reg
uint8_t SREJ : 4;
uint8_t MIN_NUM_LIG : 2;
// t_int_mask_ant
uint8_t MASK_DIST : 1;
uint8_t LCO_FDIV : 2;
// t_tun_disp
uint8_t TUN_CAP : 4;
} sens_pars_t;
typedef struct{
uint8_t restore : 1; // restore sensors' parameters on start
} flags_t;
/*
* struct to save user configurations
*/
typedef struct __attribute__((packed, aligned(4))){
uint16_t userconf_sz; // "magick number"
uint16_t iInterface[MAX_IINTERFACE_SZ]; // interface name
uint8_t iIlength; // length in symbols
sens_pars_t spars[SENSORS_AMOUNT]; // sensors' stored flags
flags_t flags; // common flags
} user_conf;
extern uint32_t maxCnum;
extern user_conf the_conf;
extern int currentconfidx;
void flashstorage_init();
int store_userconf();
int erase_storage(int npage);

View File

@@ -0,0 +1,70 @@
/*
* This file is part of the as3935 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 "adc.h"
#include "hardware.h"
#include "spi.h"
/*
* PA0 - INT0 - PD in
* PA1 - INT1 - PD in
* PA2 - CS0 - PP out
* PA3 - CS1 - PP out
* PA5 - SPI1 SCK
* PA6 - SPI1 MISO
* PA7 - SPI1 MOSI
* PA9 - USART1 Tx
* PA10- USART1 Rx
*/
static inline void gpio_setup(){
// Enable clocks to the GPIO subsystems (PB for ADC), turn on AFIO clocking to disable SWD/JTAG
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_IOPCEN | RCC_APB2ENR_AFIOEN | RCC_APB2ENR_SPI1EN;
// turn off SWJ/JTAG
AFIO->MAPR = AFIO_MAPR_SWJ_CFG_JTAGDISABLE; // for PA15
// Set led as opendrain output
GPIOC->CRH |= CRH(13, CNF_ODOUTPUT|MODE_SLOW);
// PA pins
GPIOA->ODR = 0; // for pull-down
GPIOA->CRL = CRL(0, CNF_PUDINPUT|MODE_INPUT) | CRL(1, CNF_PUDINPUT|MODE_INPUT) | CRL(2, CNF_PPOUTPUT|MODE_SLOW) |
CRL(3, CNF_PPOUTPUT|MODE_SLOW) | CRL(5, CNF_AFPP|MODE_FAST) | CRL(6, CNF_FLINPUT|MODE_INPUT) | CRL(7, CNF_AFPP|MODE_FAST);
GPIOA->CRH = CRH(9, CNF_AFPP|MODE_FAST) | CRH(10, CNF_FLINPUT|MODE_INPUT) | CRH(15, CNF_PPOUTPUT|MODE_SLOW);
CS_OFF();
}
void hw_setup(){
gpio_setup();
adc_setup();
spi_setup();
}
#ifndef EBUG
void iwdg_setup(){
uint32_t tmout = 16000000;
RCC->CSR |= RCC_CSR_LSION;
while((RCC->CSR & RCC_CSR_LSIRDY) != RCC_CSR_LSIRDY){if(--tmout == 0) break;} /* (2) */
IWDG->KR = IWDG_START;
IWDG->KR = IWDG_WRITE_ACCESS;
IWDG->PR = IWDG_PR_PR_1;
IWDG->RLR = 1250;
tmout = 16000000;
while(IWDG->SR){if(--tmout == 0) break;}
IWDG->KR = IWDG_REFRESH;
}
#endif

View File

@@ -0,0 +1,57 @@
/*
* This file is part of the as3935 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 <stm32f1.h>
// amount of connected sensors
#define SENSORS_AMOUNT 2
// LED0 - PC13 (bluepill), blinking each second
#define LED0_port GPIOC
#define LED0_pin (1<<13)
// USB pullup (not present in bluepill, should be soldered) - PA15
#define USBPU_port GPIOA
#define USBPU_pin (1<<15)
#define USBPU_ON() pin_set(USBPU_port, USBPU_pin)
#define USBPU_OFF() pin_clear(USBPU_port, USBPU_pin)
#define LED_blink(x) pin_toggle(x ## _port, x ## _pin)
#define LED_on(x) pin_clear(x ## _port, x ## _pin)
#define LED_off(x) pin_set(x ## _port, x ## _pin)
// CS pins: PA2/PA3
#define CS_OFF() do{GPIOA->BSRR = 0xc;}while(0)
#define CS(x) do{GPIOA->BSRR = (x) ? (1<<19 | 1<<2) : (1<<18 | 1<<3);}while(0)
// INT pins: PA0/PA1
#define CHK_INT(x) ((GPIOA->IDR & (1<<x)) ? 1 : 0)
enum{
DISPLCO_NOTHING,
DISPLCO_TRCO,
DISPLCO_SRCO,
DISPLCO_LCO
};
extern uint8_t DISPLCO[SENSORS_AMOUNT];
void hw_setup();
void iwdg_setup();

View File

@@ -0,0 +1,109 @@
/*
* This file is part of the as3935 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 "as3935.h"
#include "flash.h"
#include "hardware.h"
#include "commproto.h"
#include "flash.h"
#include "spi.h"
#include "strfunc.h"
#include "usb_dev.h"
uint8_t DISPLCO[SENSORS_AMOUNT] = {0};
volatile uint32_t Tms = 0;
static char inbuff[RBINSZ];
/* Called when systick fires */
void sys_tick_handler(void){
++Tms;
}
int main(){
uint8_t oldint[2] = {0}; // ==1 for interrupted pins
uint32_t lastT = 0;
StartHSE();
//flashstorage_init();
hw_setup();
USBPU_OFF();
SysTick_Config(72000);
flashstorage_init();
USB_setup();
#ifndef EBUG
iwdg_setup();
#endif
//usart_setup();
USBPU_ON();
// wake-up all sensors and run auto-calibration
for(uint8_t ch = 0; ch < SENSORS_AMOUNT; ++ch){
as3935_channel = ch;
as3935_wakeup();
as3935_calib_rco();
if(the_conf.flags.restore){
sens_pars_t *p = &the_conf.spars[ch];
as3935_gain(p->AFE_GB);
as3935_wdthres(p->WDTH);
as3935_nflev(p->NF_LEV);
as3935_srej(p->SREJ);
as3935_minnumlig(p->MIN_NUM_LIG);
as3935_maskdist(p->MASK_DIST);
as3935_lco_fdiv(p->LCO_FDIV);
as3935_tuncap(p->TUN_CAP);
}
}
while(1){
IWDG->KR = IWDG_REFRESH; // refresh watchdog
if(Tms - lastT > 499){
LED_blink(LED0);
lastT = Tms;
}
int l = USB_receivestr(inbuff, RBINSZ);
if(l < 0) USB_sendstr("ERROR: CMD USB buffer overflow or string was too long");
else if(l){
const char *ans = parse_cmd(USB_sendstr, inbuff);
if(ans) USB_sendstr(ans);
}
for(uint8_t ch = 0; ch < SENSORS_AMOUNT; ++ch){
if(DISPLCO[ch] != DISPLCO_NOTHING) continue; // don't check IRQ in if it used as clock output
if(CHK_INT(ch)){
if(oldint[ch] == 0){
oldint[ch] = 1;
uint8_t code;
as3935_channel = ch;
int result = as3935_intcode(&code);
if(!result) USB_sendstr("CANTGET\n");
else if(code){
uint8_t savedcode = code;
USB_sendstr("INTERRUPT"); USB_putbyte('0'+ch); USB_putbyte('=');
const char *delim = NULL, *comma = ",";
if(code & INT_NH){ USB_sendstr("NOICE"); delim = comma; code &= ~INT_NH; }
if(code & INT_D){ USB_sendstr(delim); USB_sendstr("DISTURBER"); delim = comma; code &= ~INT_D; }
if(code & INT_L){ USB_sendstr(delim); USB_sendstr("LIGHTNING"); code &= ~INT_L; }
if(code) USB_sendstr(u2str(code));
newline();
if(savedcode == INT_L){ // clear lightning - show distance and power
lightning_info(USB_sendstr, ch);
}
}
}
}else oldint[ch] = 0;
}
}
return 0;
}

View File

@@ -0,0 +1,4 @@
set FLASH_SIZE 0x10000
source [find interface/stlink.cfg]
source [find target/stm32f1x.cfg]

View File

@@ -0,0 +1,203 @@
/*
* Copyright 2023 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 <string.h>
#include "ringbuffer.h"
#define CHK(b) do{if(!b) return -1;}while(0)
static int datalen(ringbuffer *b){
if(b->tail >= b->head) return (b->tail - b->head);
else return (b->length - b->head + b->tail);
}
// stored data length
int RB_datalen(ringbuffer *b){
CHK(b);
if(0 == datalen(b)) return 0; // don't block for empty RO operations
if(b->busy) return -1;
b->busy = 1;
int l = datalen(b);
b->busy = 0;
return l;
}
static int hasbyte(ringbuffer *b, uint8_t byte){
if(b->head == b->tail) return -1; // no data in buffer
int startidx = b->head;
if(b->head > b->tail){ //
for(int found = b->head; found < b->length; ++found)
if(b->data[found] == byte) return found;
startidx = 0;
}
for(int found = startidx; found < b->tail; ++found)
if(b->data[found] == byte) return found;
return -1;
}
/**
* @brief RB_hasbyte - check if buffer has given byte stored
* @param b - buffer
* @param byte - byte to find
* @return index if found, -1 if none or busy
*/
int RB_hasbyte(ringbuffer *b, uint8_t byte){
CHK(b);
if(b->busy) return -1;
b->busy = 1;
int ret = hasbyte(b, byte);
b->busy = 0;
return ret;
}
// increment head or tail
TRUE_INLINE void incr(ringbuffer *b, volatile int *what, int n){
*what += n;
if(*what >= b->length) *what -= b->length;
}
static int read(ringbuffer *b, uint8_t *s, int len){
int l = datalen(b);
if(!l) return 0;
if(l > len) l = len;
int _1st = b->length - b->head;
if(_1st > l) _1st = l;
if(_1st > len) _1st = len;
memcpy(s, b->data + b->head, _1st);
if(_1st < len && l > _1st){
memcpy(s+_1st, b->data, l - _1st);
incr(b, &b->head, l);
return l;
}
incr(b, &b->head, _1st);
return _1st;
}
/**
* @brief RB_read - read data from ringbuffer
* @param b - buffer
* @param s - array to write data
* @param len - max len of `s`
* @return bytes read or -1 if busy
*/
int RB_read(ringbuffer *b, uint8_t *s, int len){
CHK(b);
if(!s || len < 1) return -1;
if(0 == datalen(b)) return 0;
if(b->busy) return -1;
b->busy = 1;
int r = read(b, s, len);
b->busy = 0;
return r;
}
// length of data from current position to `byte` (including byte)
static int lento(ringbuffer *b, uint8_t byte){
int idx = hasbyte(b, byte);
if(idx < 0) return 0;
int partlen = idx + 1 - b->head;
// now calculate length of new data portion
if(idx < b->head) partlen += b->length;
return partlen;
}
static int readto(ringbuffer *b, uint8_t byte, uint8_t *s, int len){
int partlen = lento(b, byte);
if(!partlen) return 0;
if(partlen > len) return -1;
return read(b, s, partlen);
}
/**
* @brief RB_readto fill array `s` with data until byte `byte` (with it)
* @param b - ringbuffer
* @param byte - check byte
* @param s - buffer to write data or NULL to clear data
* @param len - length of `s` or 0 to clear data
* @return amount of bytes written (negative, if len<data in buffer or buffer is busy)
*/
int RB_readto(ringbuffer *b, uint8_t byte, uint8_t *s, int len){
CHK(b);
if(!s || len < 1) return -1;
if(0 == datalen(b)) return 0;
if(b->busy) return -1;
b->busy = 1;
int n = 0;
if(s && len > 0){
n = readto(b, byte, s, len);
}else{
incr(b, &b->head, lento(b, byte)); // just throw data out
}
b->busy = 0;
return n;
}
int RB_datalento(ringbuffer *b, uint8_t byte){
CHK(b);
if(0 == datalen(b)) return 0;
if(b->busy) return -1;
b->busy = 1;
int n = lento(b, byte);
b->busy = 0;
return n;
}
// if l < rest of buffer, truncate and return actually written bytes
static int write(ringbuffer *b, const uint8_t *str, int l){
int r = b->length - 1 - datalen(b); // rest length
if(r < 1) return 0;
if(l > r) l = r;
int _1st = b->length - b->tail;
if(_1st > l) _1st = l;
memcpy(b->data + b->tail, str, _1st);
if(_1st < l){ // add another piece from start
memcpy(b->data, str+_1st, l-_1st);
}
incr(b, &b->tail, l);
return l;
}
/**
* @brief RB_write - write some data to ringbuffer
* @param b - buffer
* @param str - data
* @param l - length
* @return amount of bytes written or -1 if busy
*/
int RB_write(ringbuffer *b, const uint8_t *str, int l){
CHK(b);
if(!str || l < 1) return -1;
if(b->length - datalen(b) < 2) return 0;
if(b->busy) return -1;
b->busy = 1;
int w = write(b, str, l);
b->busy = 0;
return w;
}
// just delete all information in buffer `b`
int RB_clearbuf(ringbuffer *b){
CHK(b);
if(b->busy) return -1;
b->busy = 1;
b->head = 0;
b->tail = 0;
bzero(b->data, b->length);
b->busy = 0;
return 1;
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2023 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>
#if defined STM32F0
#include <stm32f0.h>
#elif defined STM32F1
#include <stm32f1.h>
#elif defined STM32F3
#include <stm32f3.h>
#endif
typedef struct{
uint8_t *data; // data buffer
const int length; // its length
int head; // head index
int tail; // tail index
volatile int busy; // == TRUE if buffer is busy now
} ringbuffer;
int RB_read(ringbuffer *b, uint8_t *s, int len);
int RB_readto(ringbuffer *b, uint8_t byte, uint8_t *s, int len);
int RB_hasbyte(ringbuffer *b, uint8_t byte);
int RB_write(ringbuffer *b, const uint8_t *str, int l);
int RB_datalen(ringbuffer *b);
int RB_datalento(ringbuffer *b, uint8_t byte);
int RB_clearbuf(ringbuffer *b);

View File

@@ -0,0 +1,61 @@
/*
* This file is part of the as3935 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 "spi.h"
#include "hardware.h"
#include "commproto.h"
// CR1 register default values
// Fpclk/64
static const uint32_t SPI_CR1 = SPI_CR1_MSTR | SPI_CR1_BR_2 | SPI_CR1_BR_0 | SPI_CR1_SSM | SPI_CR1_SSI | SPI_CR1_CPHA /* | SPI_CR1_CPOL*/;
spiStatus SPI_status = SPI_NOTREADY;
uint8_t as3935_channel = 0;
void spi_setup(){
// master, no slave select, BR=F/16, CPOL/CPHA - polarity.
SPI1->CR1 = SPI_CR1;
SPI_status = SPI_READY;
SPI1->CR1 |= SPI_CR1_SPE; // enable SPI
}
volatile uint32_t wctr;
#define WAITX(x) do{wctr = 0; while((x) && (++wctr < 360000)) IWDG->KR = IWDG_REFRESH; if(wctr==360000) return -1;}while(0)
/**
* @brief SPI_transmit - transmit data and receive new one
* @param buf - data to transmit/receive
* @param len - its length
* @return amount of transmitted data or -1 if error
*/
uint8_t SPI_transmit(uint8_t *buf, uint8_t len){
if(!buf || !len) return 0; // bad data format
if(SPI_status != SPI_READY) return 0; // spi not ready to transmit data
CS(as3935_channel);
for(uint8_t x = 0; x < len; ++x){
WAITX(!(SPI1->SR & SPI_SR_TXE));
SPI1->DR = buf[x];
WAITX(!(SPI1->SR & SPI_SR_BSY));
WAITX(!(SPI1->SR & SPI_SR_RXNE));
buf[x] = SPI1->DR;
}
CS_OFF();
return len;
}

View File

@@ -0,0 +1,35 @@
/*
* This file is part of the as3935 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 <stm32f1.h>
typedef enum{
SPI_NOTREADY,
SPI_READY,
SPI_BUSY
} spiStatus;
extern spiStatus SPI_status;
// SPI channel number for `CS` macro
extern uint8_t as3935_channel;
void spi_setup();
uint8_t SPI_transmit(uint8_t *buf, uint8_t len);

View File

@@ -0,0 +1,266 @@
/*
* This file is part of the encoders project.
* Copyright 2025 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 <stm32f1.h>
#include <string.h>
#include "usb_dev.h"
/**
* @brief hexdump - dump hex array by 16 bytes in string
* @param arr - array to dump
* @param len - length of `arr`
*/
void hexdump(int (*sendfun)(const char *), uint8_t *arr, uint16_t len){
char buf[52], *bptr = buf;
for(uint16_t l = 0; l < len; ++l, ++arr){
for(int16_t j = 1; j > -1; --j){
register uint8_t half = (*arr >> (4*j)) & 0x0f;
if(half < 10) *bptr++ = half + '0';
else *bptr++ = half - 10 + 'a';
}
if(l % 16 == 15){
*bptr++ = '\n';
*bptr = 0;
sendfun(buf);
bptr = buf;
}else *bptr++ = ' ';
}
if(bptr != buf){
*bptr++ = '\n';
*bptr = 0;
sendfun(buf);
}
}
/**
* @brief _2str - convert value into string buffer
* @param val - |value|
* @param minus - ==0 if value > 0
* @return buffer with number
*/
static char *_2str(uint32_t val, uint8_t minus){
static char strbuf[12];
char *bufptr = &strbuf[11];
*bufptr = 0;
if(!val){
*(--bufptr) = '0';
}else{
while(val){
uint32_t x = val / 10;
*(--bufptr) = (val - 10*x) + '0';
val = x;
}
}
if(minus) *(--bufptr) = '-';
return bufptr;
}
// return string with number `val`
char *u2str(uint32_t val){
return _2str(val, 0);
}
char *i2str(int32_t i){
uint8_t minus = 0;
uint32_t val;
if(i < 0){
minus = 1;
val = -i;
}else val = i;
return _2str(val, minus);
}
/**
* @brief uhex2str - print 32bit unsigned int as hex
* @param val - value
* @return string with number
*/
char *uhex2str(uint32_t val){
static char buf[12] = "0x";
int npos = 2;
uint8_t *ptr = (uint8_t*)&val + 3;
int8_t i, j, z=1;
for(i = 0; i < 4; ++i, --ptr){
if(*ptr == 0){ // omit leading zeros
if(i == 3) z = 0;
if(z) continue;
}
else z = 0;
for(j = 1; j > -1; --j){
uint8_t half = (*ptr >> (4*j)) & 0x0f;
if(half < 10) buf[npos++] = half + '0';
else buf[npos++] = half - 10 + 'a';
}
}
buf[npos] = 0;
return buf;
}
/**
* @brief omit_spaces - eliminate leading spaces and other trash in string
* @param buf - string
* @return - pointer to first character in `buf` > ' '
*/
char *omit_spaces(char *buf){
while(*buf){
if(*buf > ' ') break;
++buf;
}
return buf;
}
/**
* @brief getdec - read decimal number & return pointer to next non-number symbol
* @param buf - string
* @param N - number read
* @return Next non-number symbol. In case of overflow return `buf` and N==0xffffffff
*/
static char *getdec(char *buf, uint32_t *N){
char *start = (char*)buf;
uint32_t num = 0;
while(*buf){
char c = *buf;
if(c < '0' || c > '9'){
break;
}
if(num > 429496729 || (num == 429496729 && c > '5')){ // overflow
*N = 0xffffff;
return start;
}
num *= 10;
num += c - '0';
++buf;
}
*N = num;
return buf;
}
// read hexadecimal number (without 0x prefix!)
char *gethex(char *buf, uint32_t *N){
char *start = buf;
uint32_t num = 0;
while(*buf){
char c = *buf;
uint8_t M = 0;
if(c >= '0' && c <= '9'){
M = '0';
}else if(c >= 'A' && c <= 'F'){
M = 'A' - 10;
}else if(c >= 'a' && c <= 'f'){
M = 'a' - 10;
}
if(M){
if(num & 0xf0000000){ // overflow
*N = 0xffffff;
return start;
}
num <<= 4;
num += c - M;
}else{
break;
}
++buf;
}
*N = num;
return buf;
}
// read octal number (without 0 prefix!)
static char *getoct(char *buf, uint32_t *N){
char *start = (char*)buf;
uint32_t num = 0;
while(*buf){
char c = *buf;
if(c < '0' || c > '7'){
break;
}
if(num & 0xe0000000){ // overflow
*N = 0xffffff;
return start;
}
num <<= 3;
num += c - '0';
++buf;
}
*N = num;
return buf;
}
// read binary number (without b prefix!)
static char *getbin(char *buf, uint32_t *N){
char *start = (char*)buf;
uint32_t num = 0;
while(*buf){
char c = *buf;
if(c < '0' || c > '1'){
break;
}
if(num & 0x80000000){ // overflow
*N = 0xffffff;
return start;
}
num <<= 1;
if(c == '1') num |= 1;
++buf;
}
*N = num;
return buf;
}
/**
* @brief getnum - read uint32_t from string (dec, hex or bin: 127, 0x7f, 0b1111111)
* @param buf - buffer with number and so on
* @param N - the number read
* @return pointer to first non-number symbol in buf
* (if it is == buf, there's no number or if *N==0xffffffff there was overflow)
*/
char *getnum(char *txt, uint32_t *N){
char *nxt = NULL;
char *s = omit_spaces(txt);
if(*s == '0'){ // hex, oct or 0
if(s[1] == 'x' || s[1] == 'X'){ // hex
nxt = gethex(s+2, N);
if(nxt == s+2) nxt = (char*)txt;
}else if(s[1] > '0'-1 && s[1] < '8'){ // oct
nxt = getoct(s+1, N);
if(nxt == s+1) nxt = (char*)txt;
}else{ // 0
nxt = s+1;
*N = 0;
}
}else if(*s == 'b' || *s == 'B'){
nxt = getbin(s+1, N);
if(nxt == s+1) nxt = (char*)txt;
}else{
nxt = getdec(s, N);
if(nxt == s) nxt = (char*)txt;
}
return nxt;
}
// get signed integer
char *getint(char *txt, int32_t *I){
char *s = omit_spaces(txt);
int32_t sign = 1;
uint32_t U;
if(*s == '-'){
sign = -1;
++s;
}
char *nxt = getnum(s, &U);
if(nxt == s) return txt;
if(U & 0x80000000) return txt; // overfull
*I = sign * (int32_t)U;
return nxt;
}

View File

@@ -1,5 +1,5 @@
/*
* This file is part of the ir-allsky project.
* This file is part of the encoders project.
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
@@ -18,13 +18,14 @@
#pragma once
extern const char *const Timage;
#include <stdint.h>
#define SEND_USB (1)
#define SEND_USART (0)
void hexdump(int (*sendfun)(const char*), uint8_t *arr, uint16_t len);
char *u2str(uint32_t val);
char *i2str(int32_t i);
char *uhex2str(uint32_t val);
char *gethex(const char *buf, uint32_t *N);
char *getnum(char *txt, uint32_t *N);
char *omit_spaces(char *buf);
char *getint(char *txt, int32_t *I);
extern uint8_t cartoon;
void chsendfun(int sendto);
const char *parse_cmd(char *buf, int sendto);
void dumpIma(const fp_t im[MLX_PIXNO]);
void drawIma(const fp_t im[MLX_PIXNO]);

View File

@@ -0,0 +1,229 @@
/*
* Copyright 2024 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 <string.h> // memcpy
#include "flash.h" // MAX_IINTERFACE_SZ, the_conf
#include "usb_descr.h"
// low/high for uint16_t
#define L16(x) (x & 0xff)
#define H16(x) (x >> 8)
static const uint8_t USB_DeviceDescriptor[] = {
USB_DT_DEVICE_SIZE, // bLength
USB_DT_DEVICE, // bDescriptorType
L16(bcdUSB), // bcdUSB_L
H16(bcdUSB), // bcdUSB_H
USB_CLASS_MISC, // bDeviceClass
bDeviceSubClass, // bDeviceSubClass
bDeviceProtocol, // bDeviceProtocol
USB_EP0BUFSZ, // bMaxPacketSize
L16(idVendor), // idVendor_L
H16(idVendor), // idVendor_H
L16(idProduct), // idProduct_L
H16(idProduct), // idProduct_H
L16(bcdDevice_Ver), // bcdDevice_Ver_L
H16(bcdDevice_Ver), // bcdDevice_Ver_H
iMANUFACTURER_DESCR, // iManufacturer - indexes of string descriptors in array
iPRODUCT_DESCR, // iProduct
iSERIAL_DESCR, // iSerial
bNumConfigurations // bNumConfigurations
};
static const uint8_t USB_DeviceQualifierDescriptor[] = {
USB_DT_QUALIFIER_SIZE, //bLength
USB_DT_QUALIFIER, // bDescriptorType
L16(bcdUSB), // bcdUSB_L
H16(bcdUSB), // bcdUSB_H
USB_CLASS_PER_INTERFACE, // bDeviceClass
bDeviceSubClass, // bDeviceSubClass
bDeviceProtocol, // bDeviceProtocol
USB_EP0BUFSZ, // bMaxPacketSize0
bNumConfigurations, // bNumConfigurations
0 // Reserved
};
#define wTotalLength (USB_DT_CONFIG_SIZE + (bNumInterfaces * USB_DT_INTERFACE_SIZE) + (bTotNumEndpoints * USB_DT_ENDPOINT_SIZE) + (bNumCsInterfaces * USB_DT_CS_INTERFACE_SIZE) - 1)
static const uint8_t USB_ConfigDescriptor[] = {
// Configuration Descriptor
USB_DT_CONFIG_SIZE, // bLength: Configuration Descriptor size
USB_DT_CONFIG, // bDescriptorType: Configuration
L16(wTotalLength), // wTotalLength.L :no of returned bytes
H16(wTotalLength), // wTotalLength.H
bNumInterfaces, // bNumInterfaces
1, // bConfigurationValue: Current configuration value
0, // iConfiguration: Index of string descriptor describing the configuration or 0
BusPowered, // bmAttributes - Bus powered
50, // MaxPower in 2mA units
//---------------------------------------------------------------------------
// Virtual command Interface Descriptor
USB_DT_INTERFACE_SIZE, // bLength: Interface Descriptor size
USB_DT_INTERFACE, // bDescriptorType: Interface
0, // bInterfaceNumber: Number of Interface
0, // bAlternateSetting: Alternate setting
1, // bNumEndpoints: one for this
USB_CLASS_COMM, // bInterfaceClass
2, // bInterfaceSubClass: ACM
1, // bInterfaceProtocol: Common AT commands
iINTERFACE_DESCR1, // iInterface
// ---- CS Interfaces
USB_DT_CS_INTERFACE_SIZE, // bLength
USB_DT_CS_INTERFACE, // bDescriptorType: CS_INTERFACE
0, // bDescriptorSubtype: Header Func Desc
0x10, // bcdCDC: spec release number
1, // bDataInterface
USB_DT_CS_INTERFACE_SIZE, // bLength
USB_DT_CS_INTERFACE, // bDescriptorType: CS_INTERFACE
1, // bDescriptorSubtype: Call Management Func Desc
0, // bmCapabilities: D0+D1
1, // bDataInterface
USB_DT_CS_INTERFACE_SIZE-1, // bLength
USB_DT_CS_INTERFACE, // bDescriptorType: CS_INTERFACE
2, // bDescriptorSubtype: Abstract Control Management desc
2, // bmCapabilities
USB_DT_CS_INTERFACE_SIZE, // bLength
USB_DT_CS_INTERFACE, // bDescriptorType: CS_INTERFACE
6, // bDescriptorSubtype: Union func desc
0, // bMasterInterface: Communication class interface
1, // bSlaveInterface0: Data Class Interface
// Virtual endpoint 1 Descriptor
USB_DT_ENDPOINT_SIZE, // bLength: Endpoint Descriptor size
USB_DT_ENDPOINT, // bDescriptorType: Endpoint
0x8A, // bEndpointAddress IN10
USB_BM_ATTR_INTERRUPT, // bmAttributes: Interrupt
L16(USB_EP1BUFSZ), // wMaxPacketSize LO
H16(USB_EP1BUFSZ), // wMaxPacketSize HI
0x10, // bInterval: 16ms
//---------------------------------------------------------------------------
// Data interface
USB_DT_INTERFACE_SIZE, // bLength: Interface Descriptor size
USB_DT_INTERFACE, // bDescriptorType: Interface
1, // bInterfaceNumber: Number of Interface
0, // bAlternateSetting: Alternate setting
2, // bNumEndpoints: in and out
USB_CLASS_DATA, // bInterfaceClass
2, // bInterfaceSubClass: ACM
0, // bInterfaceProtocol
0, // iInterface
//Endpoint IN1 Descriptor
USB_DT_ENDPOINT_SIZE, // bLength: Endpoint Descriptor size
USB_DT_ENDPOINT, // bDescriptorType: Endpoint
0x81, // bEndpointAddress: IN1
USB_BM_ATTR_BULK, // bmAttributes: Bulk
L16(USB_TXBUFSZ), // wMaxPacketSize LO
H16(USB_TXBUFSZ), // wMaxPacketSize HI
0, // bInterval: ignore for Bulk transfer
// Endpoint OUT1 Descriptor
USB_DT_ENDPOINT_SIZE, // bLength: Endpoint Descriptor size
USB_DT_ENDPOINT, // bDescriptorType: Endpoint
0x01, // bEndpointAddress: OUT1
USB_BM_ATTR_BULK, // bmAttributes: Bulk
L16(USB_RXBUFSZ), // wMaxPacketSize LO
H16(USB_RXBUFSZ), // wMaxPacketSize HI
0, // bInterval: ignore for Bulk transfer
};
//const uint8_t HID_ReportDescriptor[];
_USB_LANG_ID_(LD, LANG_US);
_USB_STRING_(SD, u"0.0.1");
_USB_STRING_(MD, u"eddy@sao.ru");
_USB_STRING_(PD, u"AS3935 lightning detector");
// iInterface will change on initialisation by config
#define _USB_IIDESCR_(str) {sizeof(str), 0x03, str}
typedef struct{
uint8_t bLength;
uint8_t bDescriptorType;
uint16_t bString[MAX_IINTERFACE_SZ];
}iidescr_t;
static iidescr_t iid = _USB_IIDESCR_(u"lightning_det");
static const void* const StringDescriptor[iDESCR_AMOUNT] = {
[iLANGUAGE_DESCR] = &LD,
[iMANUFACTURER_DESCR] = &MD,
[iPRODUCT_DESCR] = &PD,
[iSERIAL_DESCR] = &SD,
[iINTERFACE_DESCR1] = &iid
};
static void wr0(const uint8_t *buf, uint16_t size, uint16_t askedsize){
if(askedsize < size) size = askedsize; // shortened request
if(size < USB_EP0BUFSZ){
EP_WriteIRQ(0, buf, size);
return;
}
while(size){
uint16_t l = size;
if(l > USB_EP0BUFSZ) l = USB_EP0BUFSZ;
EP_WriteIRQ(0, buf, l);
buf += l;
size -= l;
uint8_t needzlp = (l == USB_EP0BUFSZ) ? 1 : 0;
if(size || needzlp){ // send last data buffer
uint16_t epstatus = KEEP_DTOG(USB->EPnR[0]);
// keep DTOGs, clear CTR_RX,TX, set TX VALID, leave stat_Rx
USB->EPnR[0] = (epstatus & ~(USB_EPnR_CTR_RX|USB_EPnR_CTR_TX|USB_EPnR_STAT_RX))
^ USB_EPnR_STAT_TX;
uint32_t ctr = 1000000;
while(--ctr && (USB->ISTR & USB_ISTR_CTR) == 0){IWDG->KR = IWDG_REFRESH;};
if((USB->ISTR & USB_ISTR_CTR) == 0){
return;
}
if(needzlp) EP_WriteIRQ(0, NULL, 0);
}
}
}
void get_descriptor(config_pack_t *pack){
uint8_t descrtype = pack->wValue >> 8,
descridx = pack->wValue & 0xff;
switch(descrtype){
case DEVICE_DESCRIPTOR:
wr0(USB_DeviceDescriptor, sizeof(USB_DeviceDescriptor), pack->wLength);
break;
case CONFIGURATION_DESCRIPTOR:
wr0(USB_ConfigDescriptor, sizeof(USB_ConfigDescriptor), pack->wLength);
break;
case STRING_DESCRIPTOR:
if(descridx < iDESCR_AMOUNT){
wr0((const uint8_t *)StringDescriptor[descridx], *((uint8_t*)StringDescriptor[descridx]), pack->wLength);
}else{
EP_WriteIRQ(0, NULL, 0);
}
break;
case DEVICE_QUALIFIER_DESCRIPTOR:
wr0(USB_DeviceQualifierDescriptor, sizeof(USB_DeviceQualifierDescriptor), pack->wLength);
break;
/* case HID_REPORT_DESCRIPTOR:
wr0(HID_ReportDescriptor, sizeof(HID_ReportDescriptor), pack->wLength);
break;*/
default:
break;
}
}
void setup_interfaces(){
if(the_conf.iIlength){
iid.bLength = the_conf.iIlength + 2; // +2 - for bLength and bDescriptorType
memcpy(iid.bString, the_conf.iInterface, the_conf.iIlength);
}
iid.bDescriptorType = 0x03;
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2024 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>
#include "usb_lib.h"
// definition of parts common for USB_DeviceDescriptor & USB_DeviceQualifierDescriptor
// bcdUSB: 1.10
#define bcdUSB 0x0110
// Class - Misc (EF), subclass - common (2), protocol - interface association descr (1)
#define bDeviceSubClass 0x02
#define bDeviceProtocol 0x01
#define idVendor 0x0483
#define idProduct 0x5740
#define bcdDevice_Ver 0x0200
#define bNumConfigurations 1
// amount of interfaces and endpoints (except 0) used
#define bNumInterfaces 2
#define bTotNumEndpoints 3
#define bNumCsInterfaces 4
// powered
#define BusPowered (1<<7)
#define SelfPowered (1<<6)
#define RemoteWakeup (1<<5)
// buffer sizes
// for USB FS EP0 buffers are from 8 to 64 bytes long
#define USB_EP0BUFSZ 64
#define USB_EP1BUFSZ 10
// Rx/Tx EPs
#define USB_RXBUFSZ 64
#define USB_TXBUFSZ 64
// string descriptors
enum{
iLANGUAGE_DESCR,
iMANUFACTURER_DESCR,
iPRODUCT_DESCR,
iSERIAL_DESCR,
iINTERFACE_DESCR1,
iDESCR_AMOUNT
};
void get_descriptor(config_pack_t *pack);
void setup_interfaces();

View File

@@ -0,0 +1,252 @@
/*
* Copyright 2024 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 <string.h>
#include "ringbuffer.h"
#include "usb_descr.h"
#include "usb_dev.h"
// Class-Specific Control Requests
#define SEND_ENCAPSULATED_COMMAND 0x00 // unused
#define GET_ENCAPSULATED_RESPONSE 0x01 // unused
#define SET_COMM_FEATURE 0x02 // unused
#define GET_COMM_FEATURE 0x03 // unused
#define CLEAR_COMM_FEATURE 0x04 // unused
#define SET_LINE_CODING 0x20
#define GET_LINE_CODING 0x21
#define SET_CONTROL_LINE_STATE 0x22
#define SEND_BREAK 0x23
// control line states
#define CONTROL_DTR 0x01
#define CONTROL_RTS 0x02
// inbuf overflow when receiving
static volatile uint8_t bufovrfl = 0;
// receive buffer: hold data until chkin() call
static uint8_t volatile rcvbuf[USB_RXBUFSZ] __attribute__((aligned(4)));
static uint8_t volatile rcvbuflen = 0;
// line coding
usb_LineCoding lineCoding = {115200, 0, 0, 8};
// CDC configured and ready to use
volatile uint8_t CDCready = 0;
// ring buffers for incoming and outgoing data
static uint8_t obuf[RBOUTSZ], ibuf[RBINSZ];
static volatile ringbuffer rbout = {.data = obuf, .length = RBOUTSZ, .head = 0, .tail = 0};
static volatile ringbuffer rbin = {.data = ibuf, .length = RBINSZ, .head = 0, .tail = 0};
// last send data size
static volatile int lastdsz = 0;
static void chkin(){
if(bufovrfl) return; // allow user to know that previous buffer was overflowed and cleared
if(!rcvbuflen) return;
int w = RB_write((ringbuffer*)&rbin, (uint8_t*)rcvbuf, rcvbuflen);
if(w < 0){
return;
}
if(w != rcvbuflen) bufovrfl = 1;
rcvbuflen = 0;
uint16_t status = KEEP_DTOG(USB->EPnR[1]); // don't change DTOG
USB->EPnR[1] = (status & ~(USB_EPnR_STAT_TX|USB_EPnR_CTR_RX)) ^ USB_EPnR_STAT_RX; // prepare to get next data portion
}
// called from transmit EP to send next data portion or by user - when new transmission starts
static void send_next(){
uint8_t usbbuff[USB_TXBUFSZ] __attribute__((aligned(4)));
int buflen = RB_read((ringbuffer*)&rbout, (uint8_t*)usbbuff, USB_TXBUFSZ);
if(buflen == 0){
if(lastdsz == USB_TXBUFSZ) EP_Write(1, NULL, 0); // send ZLP after USB_TXBUFSZ bits packet when nothing more to send
lastdsz = 0;
return;
}else if(buflen < 0){
lastdsz = 0;
return;
}
EP_Write(1, (uint8_t*)usbbuff, buflen);
lastdsz = buflen;
}
// data IN/OUT handler
static void rxtx_handler(){
uint16_t epstatus = KEEP_DTOG(USB->EPnR[1]);
if(RX_FLAG(epstatus)){ // receive data
if(rcvbuflen){
bufovrfl = 1; // lost last data
rcvbuflen = 0;
}
rcvbuflen = EP_Read(1, (uint8_t*)rcvbuf);
USB->EPnR[1] = epstatus & ~(USB_EPnR_CTR_RX | USB_EPnR_STAT_RX | USB_EPnR_STAT_TX); // keep RX in STALL state until read data
chkin(); // try to write current data into RXbuf if it's not busy
}else{ // tx successfull
USB->EPnR[1] = (epstatus & ~(USB_EPnR_CTR_TX | USB_EPnR_STAT_TX)) ^ USB_EPnR_STAT_RX;
send_next();
}
}
// weak handlers: change them somewhere else if you want to setup USART
// SET_LINE_CODING
void linecoding_handler(usb_LineCoding *lc){
lineCoding = *lc;
}
// SET_CONTROL_LINE_STATE
void clstate_handler(uint16_t val){
CDCready = val; // CONTROL_DTR | CONTROL_RTS -> interface connected; 0 -> disconnected
}
// SEND_BREAK
void break_handler(){
CDCready = 0;
}
// USB is configured: setup endpoints
void set_configuration(){
EP_Init(1, EP_TYPE_BULK, USB_TXBUFSZ, USB_RXBUFSZ, rxtx_handler); // IN1 and OUT1
}
// PL2303 CLASS request
void usb_class_request(config_pack_t *req, uint8_t *data, uint16_t datalen){
uint8_t recipient = REQUEST_RECIPIENT(req->bmRequestType);
uint8_t dev2host = (req->bmRequestType & 0x80) ? 1 : 0;
switch(recipient){
case REQ_RECIPIENT_INTERFACE:
switch(req->bRequest){
case SET_LINE_CODING:
if(!data || !datalen) break; // wait for data
if(datalen == sizeof(usb_LineCoding))
linecoding_handler((usb_LineCoding*)data);
break;
case GET_LINE_CODING:
EP_WriteIRQ(0, (uint8_t*)&lineCoding, sizeof(lineCoding));
break;
case SET_CONTROL_LINE_STATE:
clstate_handler(req->wValue);
break;
case SEND_BREAK:
break_handler();
break;
default:
break;
}
break;
default:
if(dev2host) EP_WriteIRQ(0, NULL, 0);
}
if(!dev2host) EP_WriteIRQ(0, NULL, 0);
}
// blocking send full content of ring buffer
int USB_sendall(){
while(lastdsz > 0){
if(!CDCready) return FALSE;
}
return TRUE;
}
// put `buf` into queue to send
int USB_send(const uint8_t *buf, int len){
if(!buf || !CDCready || !len) return FALSE;
while(len){
IWDG->KR = IWDG_REFRESH;
int l = RB_datalen((ringbuffer*)&rbout);
if(l < 0) continue;
int portion = rbout.length - 1 - l;
if(portion < 1){
if(lastdsz == 0) send_next();
continue;
}
if(portion > len) portion = len;
int a = RB_write((ringbuffer*)&rbout, buf, portion);
if(a > 0){
len -= a;
buf += a;
} else if (a < 0) continue; // do nothing if buffer is in reading state
if(lastdsz == 0) send_next(); // need to run manually - all data sent, so no IRQ on IN
}
return TRUE;
}
int USB_putbyte(uint8_t byte){
if(!CDCready) return FALSE;
int l = 0;
while((l = RB_write((ringbuffer*)&rbout, &byte, 1)) != 1){
if(l < 0) continue;
}
if(lastdsz == 0) send_next(); // need to run manually - all data sent, so no IRQ on IN
return TRUE;
}
int USB_sendstr(const char *string){
if(!string || !CDCready) return FALSE;
int len = 0;
const char *b = string;
while(*b++) ++len;
if(!len) return FALSE;
return USB_send((const uint8_t*)string, len);
}
/**
* @brief USB_receive - get binary data from receiving ring-buffer
* @param buf (i) - buffer for received data
* @param len - length of `buf`
* @return amount of received bytes (negative, if overfull happened)
*/
int USB_receive(uint8_t *buf, int len){
chkin();
if(bufovrfl){
while(1 != RB_clearbuf((ringbuffer*)&rbin));
bufovrfl = 0;
return -1;
}
int sz = RB_read((ringbuffer*)&rbin, buf, len);
if(sz < 0) return 0; // buffer in writting state
return sz;
}
/**
* @brief USB_receivestr - get string up to '\n' and replace '\n' with 0
* @param buf - receiving buffer
* @param len - its length
* @return strlen or negative value indicating overflow
*/
int USB_receivestr(char *buf, int len){
chkin();
if(bufovrfl){
while(1 != RB_clearbuf((ringbuffer*)&rbin));
bufovrfl = 0;
return -1;
}
int l = RB_datalento((ringbuffer*)&rbin, '\n');
if(l > len){ // can't read: line too long -> clear it
RB_readto((ringbuffer*)&rbin, '\n', NULL, 0);
return -1;
}else if(l < 1){ // nothing or no '\n' ?
if(rbin.length == RB_datalen((ringbuffer*)&rbin)){ // buffer is full but no '\n' found
while(1 != RB_clearbuf((ringbuffer*)&rbin));
return -1;
}
return 0;
}
l = RB_readto((ringbuffer*)&rbin, '\n', (uint8_t*)buf, len);
if(l == 0) return 0;
buf[l-1] = 0; // replace '\n' with strend
return l;
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2024 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>
#include "usb_lib.h"
typedef struct {
uint32_t dwDTERate;
uint8_t bCharFormat;
#define USB_CDC_1_STOP_BITS 0
#define USB_CDC_1_5_STOP_BITS 1
#define USB_CDC_2_STOP_BITS 2
uint8_t bParityType;
#define USB_CDC_NO_PARITY 0
#define USB_CDC_ODD_PARITY 1
#define USB_CDC_EVEN_PARITY 2
#define USB_CDC_MARK_PARITY 3
#define USB_CDC_SPACE_PARITY 4
uint8_t bDataBits;
} __attribute__ ((packed)) usb_LineCoding;
extern usb_LineCoding lineCoding;
extern volatile uint8_t CDCready;
void break_handler();
void clstate_handler(uint16_t val);
void linecoding_handler(usb_LineCoding *lc);
// sizes of ringbuffers for outgoing and incoming data
#define RBOUTSZ (1024)
#define RBINSZ (1024)
#define newline() USB_putbyte('\n')
#define USND(s) do{USB_sendstr(s); USB_putbyte('\n');}while(0)
int USB_sendall();
int USB_send(const uint8_t *buf, int len);
int USB_putbyte(uint8_t byte);
int USB_sendstr(const char *string);
int USB_receive(uint8_t *buf, int len);
int USB_receivestr(char *buf, int len);

View File

@@ -0,0 +1,439 @@
/*
* Copyright 2024 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 <stdint.h>
#include "usb_lib.h"
#include "usb_descr.h"
#include "usb_dev.h"
static ep_t endpoints[STM32ENDPOINTS];
static uint16_t USB_Addr = 0;
static uint8_t setupdatabuf[EP0DATABUF_SIZE] __attribute__((aligned(4)));
static config_pack_t *setup_packet = (config_pack_t*) setupdatabuf;
volatile uint8_t usbON = 0; // device is configured and active
static uint16_t configuration = 0; // reply for GET_CONFIGURATION (==1 if configured)
static inline void std_d2h_req(){
uint16_t st = 0;
switch(setup_packet->bRequest){
case GET_DESCRIPTOR:
get_descriptor(setup_packet);
break;
case GET_STATUS:
EP_WriteIRQ(0, (uint8_t *)&st, 2); // send status: Bus Powered
break;
case GET_CONFIGURATION:
EP_WriteIRQ(0, (uint8_t*)&configuration, 1);
break;
default:
EP_WriteIRQ(0, NULL, 0);
break;
}
}
static inline void std_h2d_req(){
switch(setup_packet->bRequest){
case SET_ADDRESS:
// new address will be assigned later - after acknowlegement or request to host
USB_Addr = setup_packet->wValue;
break;
case SET_CONFIGURATION:
// Now device configured
configuration = setup_packet->wValue;
set_configuration();
usbON = 1;
break;
default:
break;
}
}
void WEAK usb_standard_request(){
uint8_t recipient = REQUEST_RECIPIENT(setup_packet->bmRequestType);
uint8_t dev2host = (setup_packet->bmRequestType & 0x80) ? 1 : 0;
switch(recipient){
case REQ_RECIPIENT_DEVICE:
if(dev2host){
std_d2h_req();
}else{
std_h2d_req();
}
break;
case REQ_RECIPIENT_INTERFACE:
if(dev2host && setup_packet->bRequest == GET_DESCRIPTOR){
get_descriptor(setup_packet);
}
break;
case REQ_RECIPIENT_ENDPOINT:
if(setup_packet->bRequest == CLEAR_FEATURE){
}else{ /* wrong */ }
break;
default:
break;
}
if(!dev2host) EP_WriteIRQ(0, NULL, 0);
}
void WEAK usb_class_request(config_pack_t *req, uint8_t _U_ *data, uint16_t _U_ datalen){
switch(req->bRequest){
case GET_INTERFACE:
break;
case SET_CONFIGURATION: // set featuring by req->wValue
break;
default:
break;
}
if(0 == (setup_packet->bmRequestType & 0x80)) // host2dev
EP_WriteIRQ(0, NULL, 0);
}
void WEAK usb_vendor_request(config_pack_t _U_ *packet, uint8_t _U_ *data, uint16_t _U_ datalen){
if(0 == (setup_packet->bmRequestType & 0x80)) // host2dev
EP_WriteIRQ(0, NULL, 0);
}
/*
bmRequestType: 76543210
7 direction: 0 - host->device, 1 - device->host
65 type: 0 - standard, 1 - class, 2 - vendor
4..0 getter: 0 - device, 1 - interface, 2 - endpoint, 3 - other
*/
/**
* Endpoint0 (control) handler
*/
static void EP0_Handler(){
uint8_t ep0dbuflen = 0;
uint8_t ep0databuf[EP0DATABUF_SIZE] __attribute__((aligned(4)));
uint16_t epstatus = KEEP_DTOG(USB->EPnR[0]); // EP0R on input -> return this value after modifications
int rxflag = RX_FLAG(epstatus);
// check direction
if(USB->ISTR & USB_ISTR_DIR){ // OUT interrupt - receive data, CTR_RX==1 (if CTR_TX == 1 - two pending transactions: receive following by transmit)
if(epstatus & USB_EPnR_SETUP){ // setup packet -> copy data to conf_pack
EP_Read(0, setupdatabuf);
// interrupt handler will be called later
}else if(epstatus & USB_EPnR_CTR_RX){ // data packet -> push received data to ep0databuf
//if(endpoints[0].rx_cnt){ }
ep0dbuflen = EP_Read(0, ep0databuf);
}
}
if(rxflag){
uint8_t reqtype = REQUEST_TYPE(setup_packet->bmRequestType);
switch(reqtype){
case REQ_TYPE_STANDARD:
if(SETUP_FLAG(epstatus)){
usb_standard_request();
}else{ }
break;
case REQ_TYPE_CLASS:
usb_class_request(setup_packet, ep0databuf, ep0dbuflen);
break;
case REQ_TYPE_VENDOR:
usb_vendor_request(setup_packet, ep0databuf, ep0dbuflen);
break;
default:
EP_WriteIRQ(0, NULL, 0);
break;
}
}
if(TX_FLAG(epstatus)){
// now we can change address after enumeration
if ((USB->DADDR & USB_DADDR_ADD) != USB_Addr){
USB->DADDR = USB_DADDR_EF | USB_Addr;
usbON = 0;
}
}
//epstatus = KEEP_DTOG(USB->EPnR[0]);
if(rxflag) epstatus ^= USB_EPnR_STAT_TX; // start ZLP or data transmission
else epstatus &= ~USB_EPnR_STAT_TX; // or leave unchanged
// keep DTOGs, clear CTR_RX,TX, set RX VALID
USB->EPnR[0] = (epstatus & ~(USB_EPnR_CTR_RX|USB_EPnR_CTR_TX)) ^ USB_EPnR_STAT_RX;
}
/**
* Write data to EP buffer (called from IRQ handler)
* @param number - EP number
* @param *buf - array with data
* @param size - its size
*/
void EP_WriteIRQ(uint8_t number, const uint8_t *buf, uint16_t size){
if(size > endpoints[number].txbufsz) size = endpoints[number].txbufsz;
#ifndef USB32
uint16_t N2 = (size + 1) >> 1;
// the buffer is 16-bit, so we should copy data as it would be uint16_t
uint16_t *buf16 = (uint16_t *)buf;
#else
int N4 = (size + 3) >> 2;
uint32_t *buf32 = (uint32_t *)buf;
#endif
#if defined USB1_16
// very bad: what if `size` is odd?
uint32_t *out = (uint32_t *)endpoints[number].tx_buf;
for(int i = 0; i < N2; ++i, ++out){
*out = buf16[i];
}
#elif defined USB2_16
// use memcpy instead?
for(int i = 0; i < N2; ++i){
endpoints[number].tx_buf[i] = buf16[i];
}
#elif defined USB32
for(int i = 0; i < N4; ++i) endpoints[number].tx_buf[i] = buf32[i];
#else
#error "Define USB1_16 / USB2_16 / USB32"
#endif
#ifndef USB32
USB_BTABLE->EP[number].USB_COUNT_TX = size;
#else
USB_BTABLE->EP[number].USB_ADDR_COUNT_TX = (USB_BTABLE->EP[number].USB_ADDR_COUNT_TX & 0xffff) | (size << 16);
#endif
}
/**
* Write data to EP buffer (called outside IRQ handler)
* @param number - EP number
* @param *buf - array with data
* @param size - its size
*/
void EP_Write(uint8_t number, const uint8_t *buf, uint16_t size){
EP_WriteIRQ(number, buf, size);
uint16_t epstatus = KEEP_DTOG(USB->EPnR[number]);
// keep DTOGs and RX stat, clear CTR_TX & set TX VALID to start transmission
USB->EPnR[number] = (epstatus & ~(USB_EPnR_CTR_TX | USB_EPnR_STAT_RX)) ^ USB_EPnR_STAT_TX;
}
/*
* Copy data from EP buffer into user buffer area
* @param *buf - user array for data
* @return amount of data read
*/
int EP_Read(uint8_t number, uint8_t *buf){
int sz = endpoints[number].rx_cnt;
if(!sz) return 0;
endpoints[number].rx_cnt = 0;
#if defined USB1_16
int n = (sz + 1) >> 1;
uint32_t *in = (uint32_t*)endpoints[number].rx_buf;
uint16_t *out = (uint16_t*)buf;
for(int i = 0; i < n; ++i, ++in)
out[i] = *(uint16_t*)in;
#elif defined USB2_16
// use memcpy instead?
for(int i = 0; i < sz; ++i)
buf[i] = endpoints[number].rx_buf[i];
#elif defined USB32
uint32_t *u32buf = (uint32_t*) buf;
int N4 = (sz + 3) >> 2;
for(int i = 0; i < N4; ++i) u32buf[i] = endpoints[number].rx_buf[i];
#else
#error "Define USB1_16 / USB2_16 / USB32"
#endif
return sz;
}
static uint16_t lastaddr = LASTADDR_DEFAULT;
/**
* Endpoint initialisation
* @param number - EP num (0...7)
* @param type - EP type (EP_TYPE_BULK, EP_TYPE_CONTROL, EP_TYPE_ISO, EP_TYPE_INTERRUPT)
* @param txsz - transmission buffer size @ USB/CAN buffer
* @param rxsz - reception buffer size @ USB/CAN buffer
* @param uint16_t (*func)(ep_t *ep) - EP handler function
* @return 0 if all OK
*/
int EP_Init(uint8_t number, uint8_t type, uint16_t txsz, uint16_t rxsz, void (*func)(ep_t ep)){
#ifdef STM32G0
// in STM32G0 all buffers should be aligned by 32 bits
if(txsz & 3) txsz = ((txsz >> 2)+1) << 2;
if(rxsz & 3) rxsz = ((rxsz >> 2)+1) << 2;
#endif
if(number >= STM32ENDPOINTS) return 4; // out of configured amount
if(txsz > USB_BTABLE_SIZE/ACCESSZ || rxsz > USB_BTABLE_SIZE/ACCESSZ) return 1; // buffer too large
if(lastaddr + txsz + rxsz >= USB_BTABLE_SIZE/ACCESSZ) return 2; // out of btable
USB->EPnR[number] = (type << 9) | (number & USB_EPnR_EA);
USB->EPnR[number] ^= USB_EPnR_STAT_RX | USB_EPnR_STAT_TX;
if(rxsz & 1) return 3; // wrong rx buffer size
uint16_t countrx = 0;
if(rxsz < 64) countrx = rxsz / 2;
else{
if(rxsz & 0x1f) return 3; // should be multiple of 32
countrx = 31 + rxsz / 32;
}
#ifdef USB32
endpoints[number].tx_buf = (uint32_t *)(USB_BTABLE_BASE + lastaddr * ACCESSZ);
#else
endpoints[number].tx_buf = (uint16_t *)(USB_BTABLE_BASE + lastaddr * ACCESSZ);
#endif
endpoints[number].txbufsz = txsz;
#ifdef USB32
USB_BTABLE->EP[number].USB_ADDR_COUNT_TX = (uint32_t) lastaddr;
#else
USB_BTABLE->EP[number].USB_ADDR_TX = lastaddr;
USB_BTABLE->EP[number].USB_COUNT_TX = 0;
#endif
lastaddr += txsz;
#ifdef USB32
endpoints[number].rx_buf = (uint32_t *)(USB_BTABLE_BASE + lastaddr * ACCESSZ);
USB_BTABLE->EP[number].USB_ADDR_COUNT_RX = (uint32_t) lastaddr | countrx << 26;
#else
endpoints[number].rx_buf = (uint8_t *)(USB_BTABLE_BASE + lastaddr * ACCESSZ);
USB_BTABLE->EP[number].USB_ADDR_RX = lastaddr;
USB_BTABLE->EP[number].USB_COUNT_RX = countrx << 10;
#endif
lastaddr += rxsz;
endpoints[number].func = func;
return 0;
}
// standard IRQ handler
void USB_IRQ(){
uint32_t CNTR = USB->CNTR;
USB->CNTR = 0;
uint32_t istr = USB->ISTR;
if(istr & USB_ISTR_RESET){
usbON = 0;
// Reinit registers
CNTR = USB_CNTR_RESETM | USB_CNTR_CTRM | USB_CNTR_SUSPM;
// Endpoint 0 - CONTROL
// ON USB LS size of EP0 may be 8 bytes, but on FS it should be 64 bytes!
lastaddr = LASTADDR_DEFAULT;
// clear address, leave only enable bit
USB->DADDR = USB_DADDR_EF;
//USB->ISTR = ~(USB_ISTR_RESET); // clear all flags
if(EP_Init(0, EP_TYPE_CONTROL, USB_EP0BUFSZ, USB_EP0BUFSZ, EP0_Handler)){
return;
};
}
if(istr & USB_ISTR_CTR){
// EP number
uint8_t n = istr & USB_ISTR_EPID;
if (istr & USB_ISTR_DIR){ // OUT
}else{ // IN
}
// copy received bytes amount
endpoints[n].rx_cnt =
#ifdef USB32
(USB_BTABLE->EP[n].USB_ADDR_COUNT_RX >> 16) & 0x3FF;
#else
USB_BTABLE->EP[n].USB_COUNT_RX & 0x3FF; // low 10 bits is counter
#endif
// call EP handler
if(endpoints[n].func) endpoints[n].func();
}
if(istr & USB_ISTR_WKUP){ // wakeup
#if defined STM32F0
CNTR &= ~(USB_CNTR_FSUSP | USB_CNTR_LPMODE | USB_CNTR_WKUPM);
#elif defined STM32G0
CNTR &= ~(USB_CNTR_SUSPEN | USB_CNTR_PDWN | USB_CNTR_WKUPM);
#else
CNTR &= ~(USB_CNTR_FSUSP | USB_CNTR_LP_MODE | USB_CNTR_WKUPM); // clear suspend flags
#endif
//USB->ISTR = ~USB_ISTR_WKUP;
}
if(istr & USB_ISTR_SUSP){ // suspend -> still no connection, may sleep
usbON = 0;
#if defined STM32F0
CNTR |= USB_CNTR_FSUSP | USB_CNTR_LPMODE | USB_CNTR_WKUPM;
#elif defined STM32G0
CNTR |= USB_CNTR_SUSPEN | USB_CNTR_WKUPM;
#else
CNTR |= USB_CNTR_FSUSP | USB_CNTR_LP_MODE | USB_CNTR_WKUPM;
#endif
CNTR &= ~(USB_CNTR_SUSPM);
//USB->ISTR = ~USB_ISTR_SUSP;
}
USB->ISTR = 0; // clear all flags
USB->CNTR = CNTR; // rewoke interrupts
}
// here we suppose that all PIN settings done in hw_setup earlier
void USB_setup(){
lastaddr = LASTADDR_DEFAULT; // clear last address settings
#if defined STM32F3
NVIC_DisableIRQ(USB_LP_IRQn);
// remap USB LP & Wakeup interrupts to 75 and 76 - works only on pure F303
RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN; // enable tacting of SYSCFG
SYSCFG->CFGR1 |= SYSCFG_CFGR1_USB_IT_RMP;
#elif defined STM32F1
NVIC_DisableIRQ(USB_LP_CAN1_RX0_IRQn);
NVIC_DisableIRQ(USB_HP_CAN1_TX_IRQn);
#elif defined STM32F0
// All is clocking from HSI48
NVIC_DisableIRQ(USB_IRQn);
RCC->APB1ENR |= RCC_APB1ENR_CRSEN;
RCC->CFGR3 &= ~RCC_CFGR3_USBSW; // reset USB
RCC->CR2 |= RCC_CR2_HSI48ON; // turn ON HSI48
uint32_t tmout = 16000000;
while(!(RCC->CR2 & RCC_CR2_HSI48RDY)){if(--tmout == 0) break;}
FLASH->ACR = FLASH_ACR_PRFTBE | FLASH_ACR_LATENCY;
CRS->CFGR &= ~CRS_CFGR_SYNCSRC;
CRS->CFGR |= CRS_CFGR_SYNCSRC_1; // USB SOF selected as sync source
CRS->CR |= CRS_CR_AUTOTRIMEN; // enable auto trim
CRS->CR |= CRS_CR_CEN; // enable freq counter & block CRS->CFGR as read-only
RCC->CFGR |= RCC_CFGR_SW;
#elif defined STM32G0
NVIC_DisableIRQ(USB_UCPD1_2_IRQn);
PWR->CR2 |= PWR_CR2_USV; // enable USB powering
//RCC->APBENR2 |= RCC_APBENR2_SYSCFGEN; // enable tacting of SYSCFG
// independent clocking of USB from HSI48
RCC->CR |= RCC_CR_HSI48ON;
uint32_t tmout = 16000000;
while(!(RCC->CR & RCC_CR_HSI48RDY)) if(--tmout == 0){ break;}
RCC->CCIPR2 &= ~RCC_CCIPR2_USBSEL; // select HSI48 for USB
RCC->APBENR1 |= RCC_APBENR1_CRSEN; // CRS clocking
CRS->CFGR = (31LL << CRS_CFGR_FELIM_Pos) | // tolerance (usually 31)
(48000LL / 1LL - 1LL) << CRS_CFGR_RELOAD_Pos | // 48MHz / 1kHZ (SOF)
CRS_CFGR_SYNCSRC_1; // USB SOF as sync source (0x2)
CRS->CR |= CRS_CR_AUTOTRIMEN | CRS_CR_CEN; // Enable autotrim and turn on Clock Recovery System
RCC->APBENR1 |= RCC_APBENR1_USBEN;
#endif
#ifndef STM32G0
RCC->APB1ENR |= RCC_APB1ENR_USBEN;
USB->CNTR = USB_CNTR_FRES; // Force USB Reset
USB->BTABLE = 0;
#else
USB->CNTR = USB_CNTR_USBRST;
#endif
for(uint32_t ctr = 0; ctr < 72000; ++ctr) nop(); // wait >1ms
USB->CNTR = USB_CNTR_RESETM; // allow only reset interrupts
USB->DADDR = 0;
USB->ISTR = 0;
#if defined STM32F3
NVIC_EnableIRQ(USB_LP_IRQn);
#elif defined STM32F1
NVIC_EnableIRQ(USB_LP_CAN1_RX0_IRQn);
#elif defined STM32F0
USB->BCDR |= USB_BCDR_DPPU;
NVIC_EnableIRQ(USB_IRQn);
#elif defined STM32G0
USB->BCDR |= USB_BCDR_DPPU; // turn ON DP pullup
NVIC_EnableIRQ(USB_UCPD1_2_IRQn);
#endif
setup_interfaces();
}
#if defined STM32F3
void usb_lp_isr() __attribute__ ((alias ("USB_IRQ")));
#elif defined STM32F1
void usb_lp_can_rx0_isr() __attribute__ ((alias ("USB_IRQ")));
#elif defined STM32F0
void usb_isr() __attribute__ ((alias ("USB_IRQ")));
#elif defined STM32G0
void usb_ucpd1_2_isr() __attribute__ ((alias ("USB_IRQ")));
#endif

View File

@@ -0,0 +1,352 @@
/*
* Copyright 2024 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>
#include <wchar.h>
#ifndef _U_
#define _U_ __attribute__((unused))
#endif
/******************************************************************
* Hardware registers etc *
*****************************************************************/
#if defined STM32F0
#include <stm32f0.h>
#elif defined STM32F1
#include <stm32f1.h>
// there's no this define in standard header
#define USB_BASE ((uint32_t)0x40005C00)
#elif defined STM32F3
#include <stm32f3.h>
#elif defined STM32G0
#include <stm32g0.h>
#endif
// max endpoints number
#define STM32ENDPOINTS 8
/**
* Buffers size definition
**/
// F0 - USB2_16; F1 - USB1_16; F3 - 1/2 depending on series; G0 - USB32
#if !defined USB1_16 && !defined USB2_16 && !defined USB32
#if defined STM32F0
#define USB2_16
#elif defined STM32F1
#define USB1_16
#elif defined STM32G0
#define USB32
#else
#error "Can't determine USB1_16/USB2_16/USB32, define by hands"
#endif
#endif
// BTABLE_SIZE FOR STM32F3:
// In STM32F303/302xB/C, 512 bytes SRAM is not shared with CAN.
// In STM32F302x6/x8 and STM32F30xxD/E, 726 bytes dedicated SRAM and 256 bytes shared SRAM with CAN i.e.
// 1Kbytes dedicated SRAM in case CAN is disabled.
// remember, that USB_BTABLE_SIZE will be divided by ACCESSZ, so don't divide it twice for 32-bit addressing
#ifdef NOCAN
#if defined STM32F0
#define USB_BTABLE_SIZE 1024
#elif defined STM32F3
#define USB_BTABLE_SIZE 1024
//#warning "Please, check real buffer size due to docs"
#else
#error "define STM32F0 or STM32F3"
#endif
#else // !NOCAN: F0/F3 with CAN or F1 (can't simultaneously run CAN and USB)
#if defined STM32F0
#define USB_BTABLE_SIZE 768
#elif defined STM32F3
#define USB_BTABLE_SIZE 768
#elif defined STM32G0
#define USB_BTABLE_SIZE 2048
//#warning "Please, check real buffer size due to docs"
#else // STM32F103: 1024 bytes but with 32-bit addressing
#define USB_BTABLE_SIZE 1024
#endif
#endif // NOCAN
// first 64 bytes of USB_BTABLE are registers!
#ifndef STM32G0
#define USB_BTABLE_BASE 0x40006000
#else
#define USB_BTABLE_BASE 0x40009800
#endif
#define USB ((USB_TypeDef *) USB_BASE)
#ifdef USB_BTABLE
#undef USB_BTABLE
#endif
#define USB_BTABLE ((USB_BtableDef *)(USB_BTABLE_BASE))
#define USB_ISTR_EPID 0x0000000F
#define USB_FNR_LSOF_0 0x00000800
#define USB_FNR_lSOF_1 0x00001000
#define USB_LPMCSR_BESL_0 0x00000010
#define USB_LPMCSR_BESL_1 0x00000020
#define USB_LPMCSR_BESL_2 0x00000040
#define USB_LPMCSR_BESL_3 0x00000080
#define USB_EPnR_CTR_RX 0x00008000
#define USB_EPnR_DTOG_RX 0x00004000
#define USB_EPnR_STAT_RX 0x00003000
#define USB_EPnR_STAT_RX_0 0x00001000
#define USB_EPnR_STAT_RX_1 0x00002000
#define USB_EPnR_SETUP 0x00000800
#define USB_EPnR_EP_TYPE 0x00000600
#define USB_EPnR_EP_TYPE_0 0x00000200
#define USB_EPnR_EP_TYPE_1 0x00000400
#define USB_EPnR_EP_KIND 0x00000100
#define USB_EPnR_CTR_TX 0x00000080
#define USB_EPnR_DTOG_TX 0x00000040
#define USB_EPnR_STAT_TX 0x00000030
#define USB_EPnR_STAT_TX_0 0x00000010
#define USB_EPnR_STAT_TX_1 0x00000020
#define USB_EPnR_EA 0x0000000F
#define USB_COUNTn_RX_BLSIZE 0x00008000
#define USB_COUNTn_NUM_BLOCK 0x00007C00
#define USB_COUNTn_RX 0x0000003F
#define USB_TypeDef USB_TypeDef_custom
typedef struct {
__IO uint32_t EPnR[STM32ENDPOINTS];
__IO uint32_t RESERVED[STM32ENDPOINTS];
__IO uint32_t CNTR;
__IO uint32_t ISTR;
__IO uint32_t FNR;
__IO uint32_t DADDR;
#ifndef USB32
__IO uint32_t BTABLE;
#else
__IO uint32_t RESERVED1; // there's no BTABLE register in STM32G0
#endif
#if defined STM32F0 || defined USB32
__IO uint32_t LPMCSR;
__IO uint32_t BCDR;
#endif
} USB_TypeDef;
// F303 D/E have 2x16 access scheme
typedef struct{
#if defined USB2_16
__IO uint16_t USB_ADDR_TX;
__IO uint16_t USB_COUNT_TX;
__IO uint16_t USB_ADDR_RX;
__IO uint16_t USB_COUNT_RX;
#define ACCESSZ (1)
#elif defined USB1_16
__IO uint32_t USB_ADDR_TX;
__IO uint32_t USB_COUNT_TX;
__IO uint32_t USB_ADDR_RX;
__IO uint32_t USB_COUNT_RX;
#define ACCESSZ (2)
#elif defined USB32
// 32-bit registers: addr & count in one!
__IO uint32_t USB_ADDR_COUNT_TX;
__IO uint32_t USB_ADDR_COUNT_RX;
#define ACCESSZ (1)
#else
#error "Define USB1_16 (16 bits over 32bit register), USB2_16 (16 bits over 16 bit register) or USB32 (32 bist over 32 bit register)"
#endif
} USB_EPDATA_TypeDef;
typedef struct{
__IO USB_EPDATA_TypeDef EP[STM32ENDPOINTS];
} USB_BtableDef;
#define EP0DATABUF_SIZE (64)
#define LASTADDR_DEFAULT (STM32ENDPOINTS * 8)
/******************************************************************
* Defines from usb.h *
*****************************************************************/
/*
* Device and/or Interface Class codes
*/
#define USB_CLASS_PER_INTERFACE 0
#define USB_CLASS_AUDIO 1
#define USB_CLASS_COMM 2
#define USB_CLASS_HID 3
#define USB_CLASS_PRINTER 7
#define USB_CLASS_PTP 6
#define USB_CLASS_MASS_STORAGE 8
#define USB_CLASS_HUB 9
#define USB_CLASS_DATA 10
#define USB_CLASS_MISC 0xef
#define USB_CLASS_VENDOR_SPEC 0xff
/*
* Descriptor types
*/
#define USB_DT_DEVICE 0x01
#define USB_DT_CONFIG 0x02
#define USB_DT_STRING 0x03
#define USB_DT_INTERFACE 0x04
#define USB_DT_ENDPOINT 0x05
#define USB_DT_QUALIFIER 0x06
#define USB_DT_IAD 0x0B
#define USB_DT_HID 0x21
#define USB_DT_REPORT 0x22
#define USB_DT_PHYSICAL 0x23
#define USB_DT_CS_INTERFACE 0x24
#define USB_DT_HUB 0x29
/*
* Descriptor sizes per descriptor type
*/
#define USB_DT_DEVICE_SIZE 18
#define USB_DT_CONFIG_SIZE 9
#define USB_DT_INTERFACE_SIZE 9
#define USB_DT_HID_SIZE 9
#define USB_DT_ENDPOINT_SIZE 7
#define USB_DT_QUALIFIER_SIZE 10
#define USB_DT_CS_INTERFACE_SIZE 5
#define USB_DT_IAD_SIZE 8
// bmRequestType & 0x80 == dev2host (1) or host2dev (0)
// recipient: bmRequestType & 0x1f
#define REQUEST_RECIPIENT(b) (b & 0x1f)
#define REQ_RECIPIENT_DEVICE 0
#define REQ_RECIPIENT_INTERFACE 1
#define REQ_RECIPIENT_ENDPOINT 2
#define REQ_RECIPIENT_OTHER 3
// type: [bmRequestType & 0x60 >> 5]
#define REQUEST_TYPE(b) ((b&0x60)>>5)
#define REQ_TYPE_STANDARD 0
#define REQ_TYPE_CLASS 1
#define REQ_TYPE_VENDOR 2
#define REQ_TYPE_RESERVED 3
//#define VENDOR_REQUEST 0x01
// standard device requests
#define GET_STATUS 0x00
#define CLEAR_FEATURE 0x01
#define SET_FEATURE 0x03
#define SET_ADDRESS 0x05
#define GET_DESCRIPTOR 0x06
#define SET_DESCRIPTOR 0x07
#define GET_CONFIGURATION 0x08
#define SET_CONFIGURATION 0x09
// and some standard interface requests
#define GET_INTERFACE 0x0A
#define SET_INTERFACE 0x0B
// and some standard endpoint requests
#define SYNC_FRAME 0x0C
// Types of descriptors
#define DEVICE_DESCRIPTOR 0x01
#define CONFIGURATION_DESCRIPTOR 0x02
#define STRING_DESCRIPTOR 0x03
#define DEVICE_QUALIFIER_DESCRIPTOR 0x06
#define DEBUG_DESCRIPTOR 0x0a
#define HID_REPORT_DESCRIPTOR 0x22
// EP types for EP_init
#define EP_TYPE_BULK 0x00
#define EP_TYPE_CONTROL 0x01
#define EP_TYPE_ISO 0x02
#define EP_TYPE_INTERRUPT 0x03
// EP types for descriptors
#define USB_BM_ATTR_CONTROL 0x00
#define USB_BM_ATTR_ISO 0x01
#define USB_BM_ATTR_BULK 0x02
#define USB_BM_ATTR_INTERRUPT 0x03
/******************************************************************
* Other stuff *
*****************************************************************/
#define RX_FLAG(epstat) (epstat & USB_EPnR_CTR_RX)
#define TX_FLAG(epstat) (epstat & USB_EPnR_CTR_TX)
#define SETUP_FLAG(epstat) (epstat & USB_EPnR_SETUP)
// EPnR bits manipulation
#define KEEP_DTOG_STAT(EPnR) (EPnR & ~(USB_EPnR_STAT_RX|USB_EPnR_STAT_TX|USB_EPnR_DTOG_RX|USB_EPnR_DTOG_TX))
#define KEEP_DTOG(EPnR) (EPnR & ~(USB_EPnR_DTOG_RX|USB_EPnR_DTOG_TX))
#define LANG_US (uint16_t)0x0409
#define _USB_STRING_(name, str) \
static const struct name \
{ \
uint8_t bLength; \
uint8_t bDescriptorType; \
uint16_t bString[(sizeof(str) - 2) / 2]; \
\
} \
name = {sizeof(name), 0x03, str}
#define _USB_LANG_ID_(name, lng_id) \
static const struct name \
{ \
uint8_t bLength; \
uint8_t bDescriptorType; \
uint16_t bString; \
\
} \
name = {0x04, 0x03, lng_id}
// EP0 configuration packet
typedef struct {
uint8_t bmRequestType;
uint8_t bRequest;
uint16_t wValue;
uint16_t wIndex;
uint16_t wLength;
} config_pack_t;
// endpoints state
typedef struct{
#ifdef USB32
uint32_t *tx_buf; // transmission buffer address
#else
uint16_t *tx_buf; // transmission buffer address
#endif
uint16_t txbufsz; // transmission buffer size
#ifdef USB32
uint32_t *rx_buf; // reception buffer address
#else
uint8_t *rx_buf; // reception buffer address
#endif
void (*func)(); // endpoint action function
unsigned rx_cnt : 10; // received data counter
} ep_t;
extern volatile uint8_t usbON;
void USB_setup();
int EP_Init(uint8_t number, uint8_t type, uint16_t txsz, uint16_t rxsz, void (*func)());
void EP_WriteIRQ(uint8_t number, const uint8_t *buf, uint16_t size);
void EP_Write(uint8_t number, const uint8_t *buf, uint16_t size);
int EP_Read(uint8_t number, uint8_t *buf);
// could be [re]defined in usb_dev.c
extern void usb_class_request(config_pack_t *packet, uint8_t *data, uint16_t datalen);
extern void usb_vendor_request(config_pack_t *packet, uint8_t *data, uint16_t datalen);
extern void set_configuration();

View File

@@ -0,0 +1,2 @@
#define BUILD_NUMBER "43"
#define BUILD_DATE "2026-04-14"

View File

@@ -6,4 +6,4 @@ LDSCRIPT ?= stm32f103xB.ld
DEFINES := -DSTM32F10X_MD
include ../makefile.f1
include ../makefile.stm32
include ../../makefile.stm32

Binary file not shown.

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 18.0.2, 2026-02-13T20:51:37. -->
<!-- Written by QtCreator 19.0.0, 2026-03-23T23:04:02. -->
<qtcreator>
<data>
<variable>EnvironmentId</variable>
@@ -154,6 +154,7 @@
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
<value type="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
<valuelist type="QVariantList" key="Analyzer.Valgrind.SuppressionFiles"/>
<value type="QList&lt;int&gt;" key="Analyzer.Valgrind.VisibleErrorKinds"></value>
<valuelist type="QVariantList" key="CustomOutputParsers"/>
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
@@ -189,6 +190,7 @@
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
<value type="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
<valuelist type="QVariantList" key="Analyzer.Valgrind.SuppressionFiles"/>
<value type="QList&lt;int&gt;" key="Analyzer.Valgrind.VisibleErrorKinds"></value>
<valuelist type="QVariantList" key="CustomOutputParsers"/>
<value type="int" key="PE.EnvironmentAspect.Base">2</value>

View File

@@ -47,8 +47,8 @@ typedef struct{
typedef struct __attribute__((packed, aligned(4))){
uint16_t userconf_sz; // "magick number"
uint16_t send232_interval; // interval (ms) of sending data to SSII over RS-232 (or 0 - not to send)
uint16_t iInterface[bTotNumEndpoints][MAX_IINTERFACE_SZ]; // hryunikod!
uint8_t iIlengths[bTotNumEndpoints];
uint16_t iInterface[InterfacesAmount][MAX_IINTERFACE_SZ]; // hryunikod!
uint8_t iIlengths[InterfacesAmount];
uint8_t encbits; // encoder bits: 26 or 32
uint8_t encbufsz; // encoder buffer size (up to ENCODER_BUFSZ_MAX)
uint8_t minzeros; // min/max zeros in preamble when searching start of record

View File

@@ -116,7 +116,7 @@ static void proc_enc(uint8_t idx){
if(CDCready[I_CMD] && the_conf.flags.debug){
CMDWR("Err, restart SPI "); USB_putbyte(I_CMD, '1'+idx); CMDn();
}
spi_start_enc(idx); // restart measurement
//spi_start_enc(idx); // restart measurement
}
if(the_conf.flags.monit) monitT[idx] = Tms;
else if(testflag) spi_start_enc(idx);
@@ -124,7 +124,7 @@ static void proc_enc(uint8_t idx){
int main(){
uint32_t lastT = 0, usartT = 0;
uint8_t oldCDCready[bTotNumEndpoints] = {0};
uint8_t oldCDCready[InterfacesAmount] = {0};
StartHSE();
flashstorage_init();
hw_setup();
@@ -148,7 +148,7 @@ int main(){
else if(l) parse_cmd(inbuff);
// check if interface connected/disconnected
// (we CAN'T do much debug output in interrupt functions like linecoding_handler etc, so do it here)
for(int i = 1; i < bTotNumEndpoints; ++i){
for(int i = 1; i < InterfacesAmount; ++i){
if(oldCDCready[i] != CDCready[i]){
CMDWR("Interface ");
CMDWR(u2str(i));

View File

@@ -133,7 +133,7 @@ static errcode_e sendenc(cmd_e idx, char *par){
}
static errcode_e setiface(cmd_e idx, char *par){
if(idx < C_setiface1 || idx >= C_setiface1 + bTotNumEndpoints) return ERR_BADCMD;
if(idx < C_setiface1 || idx >= C_setiface1 + InterfacesAmount) return ERR_BADCMD;
idx -= C_setiface1; // now it is an index of iIlengths
if(par && *par){
int l = strlen(par);
@@ -364,7 +364,7 @@ static errcode_e dumpconf(cmd_e _U_ idx, char _U_ *par){
CMDWR("userconf_sz="); CMDWR(u2str(the_conf.userconf_sz));
CMDWR("\ncurrentconfidx="); CMDWR(i2str(currentconfidx));
CMDn();
for(int i = 0; i < bTotNumEndpoints; ++i)
for(int i = 0; i < InterfacesAmount; ++i)
setiface(C_setiface1 + i, NULL);
setboolpar(C_autom, NULL);
setuintpar(C_amperiod, NULL);

View File

@@ -15,9 +15,12 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h> // memcpy
#include <string.h>
#include "ringbuffer.h"
#define CHK(b) do{if(!b) return -1;}while(0)
static int datalen(ringbuffer *b){
if(b->tail >= b->head) return (b->tail - b->head);
else return (b->length - b->head + b->tail);
@@ -25,6 +28,8 @@ static int datalen(ringbuffer *b){
// stored data length
int RB_datalen(ringbuffer *b){
CHK(b);
if(0 == datalen(b)) return 0; // don't block for empty RO operations
if(b->busy) return -1;
b->busy = 1;
int l = datalen(b);
@@ -52,17 +57,13 @@ static int hasbyte(ringbuffer *b, uint8_t byte){
* @return index if found, -1 if none or busy
*/
int RB_hasbyte(ringbuffer *b, uint8_t byte){
CHK(b);
if(b->busy) return -1;
b->busy = 1;
int ret = hasbyte(b, byte);
b->busy = 0;
return ret;
}
/*
// poor memcpy
static void mcpy(uint8_t *targ, const uint8_t *src, int l){
while(l--) *targ++ = *src++;
}*/
// increment head or tail
TRUE_INLINE void incr(ringbuffer *b, volatile int *what, int n){
@@ -77,10 +78,8 @@ static int read(ringbuffer *b, uint8_t *s, int len){
int _1st = b->length - b->head;
if(_1st > l) _1st = l;
if(_1st > len) _1st = len;
//mcpy(s, b->data + b->head, _1st);
memcpy(s, b->data + b->head, _1st);
if(_1st < len && l > _1st){
//mcpy(s+_1st, b->data, l - _1st);
memcpy(s+_1st, b->data, l - _1st);
incr(b, &b->head, l);
return l;
@@ -97,6 +96,9 @@ static int read(ringbuffer *b, uint8_t *s, int len){
* @return bytes read or -1 if busy
*/
int RB_read(ringbuffer *b, uint8_t *s, int len){
CHK(b);
if(!s || len < 1) return -1;
if(0 == datalen(b)) return 0;
if(b->busy) return -1;
b->busy = 1;
int r = read(b, s, len);
@@ -104,13 +106,20 @@ int RB_read(ringbuffer *b, uint8_t *s, int len){
return r;
}
static int readto(ringbuffer *b, uint8_t byte, uint8_t *s, int len){
// length of data from current position to `byte` (including byte)
static int lento(ringbuffer *b, uint8_t byte){
int idx = hasbyte(b, byte);
if(idx < 0) return 0;
int partlen = idx + 1 - b->head;
// now calculate length of new data portion
if(idx < b->head) partlen += b->length;
if(partlen > len) return -read(b, s, len);
return partlen;
}
static int readto(ringbuffer *b, uint8_t byte, uint8_t *s, int len){
int partlen = lento(b, byte);
if(!partlen) return 0;
if(partlen > len) return -1;
return read(b, s, partlen);
}
@@ -118,27 +127,45 @@ static int readto(ringbuffer *b, uint8_t byte, uint8_t *s, int len){
* @brief RB_readto fill array `s` with data until byte `byte` (with it)
* @param b - ringbuffer
* @param byte - check byte
* @param s - buffer to write data
* @param len - length of `s`
* @param s - buffer to write data or NULL to clear data
* @param len - length of `s` or 0 to clear data
* @return amount of bytes written (negative, if len<data in buffer or buffer is busy)
*/
int RB_readto(ringbuffer *b, uint8_t byte, uint8_t *s, int len){
CHK(b);
if(!s || len < 1) return -1;
if(0 == datalen(b)) return 0;
if(b->busy) return -1;
b->busy = 1;
int n = readto(b, byte, s, len);
int n = 0;
if(s && len > 0){
n = readto(b, byte, s, len);
}else{
incr(b, &b->head, lento(b, byte)); // just throw data out
}
b->busy = 0;
return n;
}
int RB_datalento(ringbuffer *b, uint8_t byte){
CHK(b);
if(0 == datalen(b)) return 0;
if(b->busy) return -1;
b->busy = 1;
int n = lento(b, byte);
b->busy = 0;
return n;
}
// if l < rest of buffer, truncate and return actually written bytes
static int write(ringbuffer *b, const uint8_t *str, int l){
int r = b->length - 1 - datalen(b); // rest length
if(l > r || !l) return 0;
if(r < 1) return 0;
if(l > r) l = r;
int _1st = b->length - b->tail;
if(_1st > l) _1st = l;
//mcpy(b->data + b->tail, str, _1st);
memcpy(b->data + b->tail, str, _1st);
if(_1st < l){ // add another piece from start
//mcpy(b->data, str+_1st, l-_1st);
memcpy(b->data, str+_1st, l-_1st);
}
incr(b, &b->tail, l);
@@ -153,6 +180,9 @@ static int write(ringbuffer *b, const uint8_t *str, int l){
* @return amount of bytes written or -1 if busy
*/
int RB_write(ringbuffer *b, const uint8_t *str, int l){
CHK(b);
if(!str || l < 1) return -1;
if(b->length - datalen(b) < 2) return 0;
if(b->busy) return -1;
b->busy = 1;
int w = write(b, str, l);
@@ -162,10 +192,12 @@ int RB_write(ringbuffer *b, const uint8_t *str, int l){
// just delete all information in buffer `b`
int RB_clearbuf(ringbuffer *b){
CHK(b);
if(b->busy) return -1;
b->busy = 1;
b->head = 0;
b->tail = 0;
bzero(b->data, b->length);
b->busy = 0;
return 1;
}

View File

@@ -17,6 +17,8 @@
#pragma once
#include <stdint.h>
#if defined STM32F0
#include <stm32f0.h>
#elif defined STM32F1
@@ -25,14 +27,12 @@
#include <stm32f3.h>
#endif
#include <stdatomic.h>
typedef struct{
uint8_t *data; // data buffer
const int length; // its length
int head; // head index
int tail; // tail index
volatile atomic_int busy; // == TRUE if buffer is busy now
volatile int busy; // == TRUE if buffer is busy now
} ringbuffer;
int RB_read(ringbuffer *b, uint8_t *s, int len);
@@ -40,4 +40,5 @@ int RB_readto(ringbuffer *b, uint8_t byte, uint8_t *s, int len);
int RB_hasbyte(ringbuffer *b, uint8_t byte);
int RB_write(ringbuffer *b, const uint8_t *str, int l);
int RB_datalen(ringbuffer *b);
int RB_datalento(ringbuffer *b, uint8_t byte);
int RB_clearbuf(ringbuffer *b);

View File

@@ -20,11 +20,6 @@
#include "flash.h"
#include "usb_descr.h"
#undef DBG
#define DBG(x)
#undef DBGs
#define DBGs(x)
// low/high for uint16_t
#define L16(x) (x & 0xff)
#define H16(x) (x >> 8)
@@ -63,7 +58,7 @@ static const uint8_t USB_DeviceQualifierDescriptor[] = {
0 // Reserved
};
#define wTotalLength (USB_DT_CONFIG_SIZE + bTotNumEndpoints * 66)
#define wTotalLength (USB_DT_CONFIG_SIZE + (bTotNumEndpoints * 66))
/*
* _1stI - number of first interface
@@ -166,7 +161,7 @@ static const uint8_t USB_ConfigDescriptor[] = {
//const uint8_t HID_ReportDescriptor[];
_USB_LANG_ID_(LD, LANG_US);
_USB_STRING_(SD, u"0.0.1");
_USB_STRING_(SD, u"0.0.2");
_USB_STRING_(MD, u"eddy@sao.ru");
_USB_STRING_(PD, u"USB BISS-C encoders controller");
@@ -177,7 +172,7 @@ typedef struct{
uint8_t bDescriptorType;
uint16_t bString[MAX_IINTERFACE_SZ];
}iidescr_t;
static iidescr_t iids[bTotNumEndpoints] = {
static iidescr_t iids[InterfacesAmount] = {
_USB_IIDESCR_(u"encoder_cmd"),
_USB_IIDESCR_(u"encoder_X"),
_USB_IIDESCR_(u"encoder_Y"),
@@ -197,12 +192,8 @@ static void wr0(const uint8_t *buf, uint16_t size, uint16_t askedsize){
if(askedsize < size) size = askedsize; // shortened request
if(size < USB_EP0BUFSZ){
EP_WriteIRQ(0, buf, size);
DBG("short wr0");
DBGs(uhex2str(size));
return;
}
DBG("long wr0");
DBGs(uhex2str(size));
while(size){
uint16_t l = size;
if(l > USB_EP0BUFSZ) l = USB_EP0BUFSZ;
@@ -215,7 +206,7 @@ static void wr0(const uint8_t *buf, uint16_t size, uint16_t askedsize){
// keep DTOGs, clear CTR_RX,TX, set TX VALID, leave stat_Rx
USB->EPnR[0] = (epstatus & ~(USB_EPnR_CTR_RX|USB_EPnR_CTR_TX|USB_EPnR_STAT_RX))
^ USB_EPnR_STAT_TX;
uint32_t ctr = 10000;
uint32_t ctr = 1000000;
while(--ctr && (USB->ISTR & USB_ISTR_CTR) == 0){IWDG->KR = IWDG_REFRESH;};
if((USB->ISTR & USB_ISTR_CTR) == 0){
return;
@@ -229,42 +220,32 @@ void get_descriptor(config_pack_t *pack){
uint8_t descrtype = pack->wValue >> 8,
descridx = pack->wValue & 0xff;
switch(descrtype){
case DEVICE_DESCRIPTOR:
DBG("DEVICE_DESCRIPTOR");
wr0(USB_DeviceDescriptor, sizeof(USB_DeviceDescriptor), pack->wLength);
break;
case CONFIGURATION_DESCRIPTOR:
DBG("CONFIGURATION_DESCRIPTOR");
wr0(USB_ConfigDescriptor, sizeof(USB_ConfigDescriptor), pack->wLength);
break;
case STRING_DESCRIPTOR:
DBG("STRING_DESCRIPTOR");
if(descridx < iDESCR_AMOUNT){
wr0((const uint8_t *)StringDescriptor[descridx], *((uint8_t*)StringDescriptor[descridx]), pack->wLength);
DBGs(uhex2str(descridx));
}else{
EP_WriteIRQ(0, NULL, 0);
DBG("Wrong index");
DBGs(uhex2str(descridx));
}
break;
case DEVICE_QUALIFIER_DESCRIPTOR:
DBG("DEVICE_QUALIFIER_DESCRIPTOR");
wr0(USB_DeviceQualifierDescriptor, sizeof(USB_DeviceQualifierDescriptor), pack->wLength);
break;
/* case HID_REPORT_DESCRIPTOR:
wr0(HID_ReportDescriptor, sizeof(HID_ReportDescriptor), pack->wLength);
break;*/
default:
break;
case DEVICE_DESCRIPTOR:
wr0(USB_DeviceDescriptor, sizeof(USB_DeviceDescriptor), pack->wLength);
break;
case CONFIGURATION_DESCRIPTOR:
wr0(USB_ConfigDescriptor, sizeof(USB_ConfigDescriptor), pack->wLength);
break;
case STRING_DESCRIPTOR:
if(descridx < iDESCR_AMOUNT){
wr0((const uint8_t *)StringDescriptor[descridx], *((uint8_t*)StringDescriptor[descridx]), pack->wLength);
}else{
EP_WriteIRQ(0, NULL, 0);
}
break;
case DEVICE_QUALIFIER_DESCRIPTOR:
wr0(USB_DeviceQualifierDescriptor, sizeof(USB_DeviceQualifierDescriptor), pack->wLength);
break;
default:
break;
}
}
// change values of iInterface by content of global config
void setup_interfaces(){
for(int i = 0; i < bTotNumEndpoints; ++i){
for(int i = 0; i < InterfacesAmount; ++i){
if(the_conf.iIlengths[i]){
iids[i].bLength = the_conf.iIlengths[i];
iids[i].bLength = the_conf.iIlengths[i] + 2; // +2 - for bLength and bDescriptorType
memcpy(iids[i].bString, the_conf.iInterface[i], the_conf.iIlengths[i]);
}
iids[i].bDescriptorType = 0x03;

View File

@@ -32,8 +32,16 @@
#define bNumConfigurations 1
// amount of interfaces and endpoints (except 0) used
#define bNumInterfaces 6
#define bTotNumEndpoints 3
#define InterfacesAmount 3
// EP number of interface
#define EPNO(i) (i + 1)
// interface number of EPno
#define IFNO(e) (e - 1)
// amount of interfaces (including virtual) except 0
#define bNumInterfaces (2*InterfacesAmount)
// amount of endpoints used
#define bTotNumEndpoints (1+InterfacesAmount)
// powered
#define BusPowered (1<<7)

View File

@@ -38,56 +38,41 @@
#define CONTROL_DTR 0x01
#define CONTROL_RTS 0x02
// It's good to use debug here ONLY to debug into USART!
// never try to debug USB into USB!!!
#undef DBG
#define DBG(x)
#undef DBGs
#define DBGs(x)
extern volatile uint32_t Tms;
// inbuf overflow when receiving
static volatile uint8_t bufovrfl[bTotNumEndpoints] = {0};
static volatile uint8_t bufovrfl[InterfacesAmount] = {0};
// receive buffer: hold data until chkin() call
static uint8_t volatile rcvbuf[bTotNumEndpoints][USB_RXBUFSZ];
static uint8_t volatile rcvbuflen[bTotNumEndpoints] = {0};
static uint8_t volatile rcvbuf[InterfacesAmount][USB_RXBUFSZ] __attribute__((aligned(4)));
static uint8_t volatile rcvbuflen[InterfacesAmount] = {0};
// line coding
#define DEFLC {115200, 0, 0, 8}
static usb_LineCoding lineCoding[bTotNumEndpoints] = {DEFLC, DEFLC, DEFLC};
static usb_LineCoding lineCoding[InterfacesAmount] = {DEFLC, DEFLC, DEFLC};
// CDC configured and ready to use
volatile uint8_t CDCready[bTotNumEndpoints] = {0};
volatile uint8_t CDCready[InterfacesAmount] = {0};
// ring buffers for incoming and outgoing data
static uint8_t obuf[bTotNumEndpoints][RBOUTSZ], ibuf[bTotNumEndpoints][RBINSZ];
static uint8_t obuf[InterfacesAmount][RBOUTSZ], ibuf[InterfacesAmount][RBINSZ];
#define OBUF(N) {.data = obuf[N], .length = RBOUTSZ, .head = 0, .tail = 0}
static volatile ringbuffer rbout[bTotNumEndpoints] = {OBUF(0), OBUF(1), OBUF(2)};
static volatile ringbuffer rbout[InterfacesAmount] = {OBUF(0), OBUF(1), OBUF(2)};
#define IBUF(N) {.data = ibuf[N], .length = RBINSZ, .head = 0, .tail = 0}
static volatile ringbuffer rbin[bTotNumEndpoints] = {IBUF(0), IBUF(1), IBUF(2)};
static volatile ringbuffer rbin[InterfacesAmount] = {IBUF(0), IBUF(1), IBUF(2)};
// last send data size (<0 if USB transfer ready)
static volatile int lastdsz[bTotNumEndpoints] = {-1, -1, -1};
static volatile int lastdsz[InterfacesAmount] = {-1, -1, -1};
// check incoming data and set ACK if need
static void chkin(uint8_t ifno){
static int ovrflctr = 0; // "antistall" counter
if(bufovrfl[ifno]) return; // allow user to know that previous buffer was overflowed and cleared
if(!rcvbuflen[ifno]) return;
int w = RB_write((ringbuffer*)&rbin[ifno], (uint8_t*)rcvbuf[ifno], rcvbuflen[ifno]);
if(w < 0){ // buffer busy
DBG("Can't write into buffer: busy");
if(w < 0){
return;
}else if(w == 0){ // no enough space or (WTF) incoming string larger than buffer size
if(rcvbuflen[ifno] > rbin[ifno].length || ++ovrflctr > 9999){
bufovrfl[ifno] = 1; // real overflow in case if ringbuffer's size less than USB buffer
ovrflctr = 0;
}else{
return; // not enough space
}
}
DBG("Put data into buffer");
if(w != rcvbuflen[ifno]) bufovrfl[ifno] = 1;
rcvbuflen[ifno] = 0;
uint16_t status = KEEP_DTOG(USB->EPnR[1+ifno]); // don't change DTOG
USB->EPnR[1+ifno] = (status & ~(USB_EPnR_STAT_TX|USB_EPnR_CTR_RX)) ^ USB_EPnR_STAT_RX; // prepare to get next data portion
uint16_t status = KEEP_DTOG(USB->EPnR[EPNO(ifno)]); // don't change DTOG
USB->EPnR[EPNO(ifno)] = (status & ~(USB_EPnR_STAT_TX|USB_EPnR_CTR_RX)) ^ USB_EPnR_STAT_RX; // prepare to get next data portion
}
// called from transmit EP to send next data portion or by user - when new transmission starts
@@ -100,97 +85,83 @@ static void send_next(uint8_t ifno){
}
if(buflen == 0){
if(lastdsz[ifno] == USB_TXBUFSZ){
EP_Write(1+ifno, NULL, 0); // send ZLP after 64 bits packet when nothing more to send
EP_Write(EPNO(ifno), NULL, 0); // send ZLP after 64 bits packet when nothing more to send
lastdsz[ifno] = 0;
}else lastdsz[ifno] = -1; // OK. User can start sending data
return;
}else if(buflen < 0){
DBG("Buff busy");
lastdsz[ifno] = -1;
return;
}
DBG("Got data in buf");
DBGs(uhex2str(buflen));
DBGs(uhex2str(ifno));
EP_Write(1+ifno, (uint8_t*)usbbuff, buflen);
EP_Write(EPNO(ifno), (uint8_t*)usbbuff, buflen);
lastdsz[ifno] = buflen;
}
// data IN/OUT handler
static void rxtx_handler(){
uint8_t epno = (USB->ISTR & USB_ISTR_EPID), ifno = epno - 1;
DBG("rxtx_handler");
DBGs(uhex2str(ifno));
if(epno > bTotNumEndpoints){
DBG("wrong ifno");
uint8_t epno = (USB->ISTR & USB_ISTR_EPID), ifno = IFNO(epno);
if(ifno > InterfacesAmount-1){
return;
}
uint16_t epstatus = KEEP_DTOG(USB->EPnR[epno]);
if(RX_FLAG(epstatus)){ // receive data
DBG("Got data");
if(rcvbuflen[ifno]){
bufovrfl[ifno] = 1; // lost last data
rcvbuflen[ifno] = 0;
DBG("OVERFULL");
}
rcvbuflen[ifno] = EP_Read(epno, (uint8_t*)rcvbuf[ifno]);
DBGs(uhex2str(rcvbuflen[ifno]));
USB->EPnR[epno] = epstatus & ~(USB_EPnR_CTR_RX | USB_EPnR_STAT_RX | USB_EPnR_STAT_TX); // keep RX in STALL state until read data
chkin(ifno); // try to write current data into RXbuf if it's not busy
}else{ // tx successfull
DBG("Tx OK");
USB->EPnR[epno] = (epstatus & ~(USB_EPnR_CTR_TX | USB_EPnR_STAT_TX)) ^ USB_EPnR_STAT_RX;
send_next(ifno);
}
}
// weak handlers: change them somewhere else if you want to setup USART
// SET_LINE_CODING
void WEAK linecoding_handler(uint8_t ifno, usb_LineCoding *lc){
lineCoding[ifno] = *lc;
DBG("linecoding_handler");
DBGs(uhex2str(ifno));
}
static void clearbufs(uint8_t ifno){
static void clearRbuf(uint8_t ifno){
uint32_t T0 = Tms;
while(Tms - T0 < 10){ // wait no more than 10ms
if(1 == RB_clearbuf((ringbuffer*)&rbin[ifno])) break;
}
T0 = Tms;
}
static void clearTbuf(uint8_t ifno){
uint32_t T0 = Tms;
while(Tms - T0 < 10){
if(1 == RB_clearbuf((ringbuffer*)&rbout[ifno])) break;
}
rcvbuflen[ifno] = 0;
}
// SET_LINE_CODING
void linecoding_handler(uint8_t ifno, usb_LineCoding *lc){
lineCoding[ifno] = *lc;
}
// SET_CONTROL_LINE_STATE
void WEAK clstate_handler(uint8_t ifno, uint16_t val){
DBG("clstate_handler");
DBGs(uhex2str(ifno));
DBGs(uhex2str(val));
if(val) clearbufs(ifno); // clear buffers on connect
void clstate_handler(uint8_t ifno, uint16_t val){
CDCready[ifno] = val; // CONTROL_DTR | CONTROL_RTS -> interface connected; 0 -> disconnected
lastdsz[ifno] = -1;
if(val){
clearRbuf(ifno);
clearTbuf(ifno);
EP_reset(EPNO(ifno));
}
}
// SEND_BREAK - disconnect interface and clear its buffers
void WEAK break_handler(uint8_t ifno){
// this is a fake handler as classic CDC ACM never receives this
void break_handler(uint8_t ifno){
CDCready[ifno] = 0;
DBG("break_handler()");
DBGs(uhex2str(ifno));
}
// USB is configured: setup endpoints
// Interface is configured: setup endpoints
void set_configuration(){
DBG("set_configuration()");
for(int i = 0; i < bTotNumEndpoints; ++i){
for(int i = 0; i < InterfacesAmount; ++i){
IWDG->KR = IWDG_REFRESH;
int r = EP_Init(1+i, EP_TYPE_BULK, USB_TXBUFSZ, USB_RXBUFSZ, rxtx_handler);
int r = EP_Init(EPNO(i), EP_TYPE_BULK, USB_TXBUFSZ, USB_RXBUFSZ, rxtx_handler);
if(r){
DBG("Can't init EP");
DBGs(uhex2str(i));
DBGs(uhex2str(r));
// OOPS, can't init EP. What to do? Cry?
break;
}
}
}
@@ -200,45 +171,33 @@ void usb_class_request(config_pack_t *req, uint8_t *data, uint16_t datalen){
uint8_t recipient = REQUEST_RECIPIENT(req->bmRequestType);
uint8_t dev2host = (req->bmRequestType & 0x80) ? 1 : 0;
uint8_t ifno = req->wIndex >> 1;
if(ifno > bTotNumEndpoints-1 && ifno != 0xff){
DBG("wrong ifno");
if(ifno > InterfacesAmount-1){ // wrong interface number
EP_WriteIRQ(0, NULL, 0);
return;
}
DBG("usb_class_request");
DBGs(uhex2str(req->bRequest));
switch(recipient){
case REQ_RECIPIENT_INTERFACE:
switch(req->bRequest){
case SET_LINE_CODING:
DBG("SET_LINE_CODING");
if(!data || !datalen) break; // wait for data
if(datalen == sizeof(usb_LineCoding))
linecoding_handler(ifno, (usb_LineCoding*)data);
break;
case GET_LINE_CODING:
DBG("GET_LINE_CODING");
EP_WriteIRQ(0, (uint8_t*)&lineCoding[ifno], sizeof(lineCoding));
break;
case SET_CONTROL_LINE_STATE:
DBG("SET_CONTROL_LINE_STATE");
clstate_handler(ifno, req->wValue);
break;
case SEND_BREAK:
DBG("SEND_BREAK");
break_handler(ifno);
break;
default:
DBG("Wrong");
DBGs(uhex2str(req->bRequest));
DBGs(uhex2str(datalen));
}
case REQ_RECIPIENT_INTERFACE:
switch(req->bRequest){
case SET_LINE_CODING:
if(!data || !datalen) break; // wait for data
if(datalen == sizeof(usb_LineCoding))
linecoding_handler(ifno, (usb_LineCoding*)data);
break;
case GET_LINE_CODING:
EP_WriteIRQ(0, (uint8_t*)&lineCoding[ifno], sizeof(lineCoding));
break;
case SET_CONTROL_LINE_STATE:
clstate_handler(ifno, req->wValue);
break;
case SEND_BREAK:
break_handler(ifno);
break;
default: // WTF?
break;
}
break;
default:
DBG("Wrong");
DBGs(uhex2str(recipient));
DBGs(uhex2str(datalen));
DBGs(uhex2str(req->bRequest));
if(dev2host) EP_WriteIRQ(0, NULL, 0);
default: // WTF?
if(dev2host) EP_WriteIRQ(0, NULL, 0);
}
if(!dev2host) EP_WriteIRQ(0, NULL, 0);
}
@@ -248,7 +207,6 @@ int USB_sendall(uint8_t ifno){
uint32_t T0 = Tms;
while(lastdsz[ifno] > 0){
if(Tms - T0 > DISCONN_TMOUT){
break_handler(ifno);
return FALSE;
}
if(!CDCready[ifno]) return FALSE;
@@ -257,17 +215,25 @@ int USB_sendall(uint8_t ifno){
return TRUE;
}
// return amount of free space in buffer
int USB_sendbufspace(uint8_t ifno){
if(!CDCready[ifno]) return 0;
return rbout[ifno].length - RB_datalen((ringbuffer*)&rbout[ifno]);
}
// put `buf` into queue to send
int USB_send(uint8_t ifno, const uint8_t *buf, int len){
if(!buf || !CDCready[ifno] || !len) return FALSE;
DBG("USB_send");
if(!buf || !CDCready[ifno] || !len){
return FALSE;
}
uint32_t T0 = Tms;
while(len){
if(Tms - T0 > DISCONN_TMOUT){
break_handler(ifno);
return FALSE;
}
if(!CDCready[ifno]) return FALSE;
if(!CDCready[ifno]){
return FALSE;
}
IWDG->KR = IWDG_REFRESH;
int l = RB_datalen((ringbuffer*)&rbout[ifno]);
if(l < 0) continue;
@@ -285,7 +251,9 @@ int USB_send(uint8_t ifno, const uint8_t *buf, int len){
if(lastdsz[ifno] < 0) send_next(ifno);
}
}
if(buf[len-1] == '\n' && lastdsz[ifno] < 0) send_next(ifno);
if(buf[len-1] == '\n' && lastdsz[ifno] < 0){
send_next(ifno);
}
return TRUE;
}
@@ -295,7 +263,6 @@ int USB_putbyte(uint8_t ifno, uint8_t byte){
uint32_t T0 = Tms;
while((l = RB_write((ringbuffer*)&rbout[ifno], &byte, 1)) != 1){
if(Tms - T0 > DISCONN_TMOUT){
break_handler(ifno);
return FALSE;
}
if(!CDCready[ifno]) return FALSE;
@@ -306,7 +273,9 @@ int USB_putbyte(uint8_t ifno, uint8_t byte){
}
}
// send line if got EOL
if(byte == '\n' && lastdsz[ifno] < 0) send_next(ifno);
if(byte == '\n' && lastdsz[ifno] < 0){
send_next(ifno);
}
return TRUE;
}
@@ -317,6 +286,10 @@ int USB_sendstr(uint8_t ifno, const char *string){
return USB_send(ifno, (const uint8_t*)string, len);
}
int USB_rcvlen(uint8_t ifno){
return RB_datalen((ringbuffer*)&rbin[ifno]);
}
/**
* @brief USB_receive - get binary data from receiving ring-buffer
* @param buf (i) - buffer for received data
@@ -324,17 +297,15 @@ int USB_sendstr(uint8_t ifno, const char *string){
* @return amount of received bytes (negative, if overfull happened)
*/
int USB_receive(uint8_t ifno, uint8_t *buf, int len){
if(!CDCready[ifno]) return 0;
chkin(ifno); // rxtx_handler could leave last message unwritten if buffer was busy
if(bufovrfl[ifno]){
DBG("Buffer overflow");
DBGs(uhex2str(ifno));
while(1 != RB_clearbuf((ringbuffer*)&rbin[ifno])); // run watchdog in case of problems
clearRbuf(ifno);
bufovrfl[ifno] = 0;
return -1;
}
int sz = RB_read((ringbuffer*)&rbin[ifno], buf, len);
if(sz < 0) return 0; // buffer in writting state
DBG("usb read");
return sz;
}
@@ -345,24 +316,22 @@ int USB_receive(uint8_t ifno, uint8_t *buf, int len){
* @return strlen or negative value indicating overflow (if so, string won't be ends with 0 and buffer should be cleared)
*/
int USB_receivestr(uint8_t ifno, char *buf, int len){
if(!CDCready[ifno]) return 0;
chkin(ifno); // rxtx_handler could leave last message unwritten if buffer was busy
if(bufovrfl[ifno]){
while(1 != RB_clearbuf((ringbuffer*)&rbin[ifno]));
clearRbuf(ifno);
bufovrfl[ifno] = 0;
return -1;
}
int l = RB_readto((ringbuffer*)&rbin[ifno], '\n', (uint8_t*)buf, len);
if(l < 1){
//if(rbin[ifno].length < 1 + RB_datalen((ringbuffer*)&rbin[ifno])){ // buffer is full but no '\n' found
if(RB_datalen((ringbuffer*)&rbin[ifno]) >= len){
CMDWRn("OVERFULL!");
while(1 != RB_clearbuf((ringbuffer*)&rbin[ifno]));
if((rbin[ifno].length <= RB_datalen((ringbuffer*)&rbin[ifno]) + 1) ||
(RB_datalento((ringbuffer*)&rbin[ifno], '\n') > len - 1)){ // buffer is full but no '\n' found or string too long
clearRbuf(ifno);
return -1;
}
return 0;
}
if(l == 0) return 0;
buf[l-1] = 0; // replace '\n' with strend
return l;
}

View File

@@ -40,14 +40,14 @@ typedef struct {
uint8_t bDataBits;
} __attribute__ ((packed)) usb_LineCoding;
extern volatile uint8_t CDCready[bTotNumEndpoints];
extern volatile uint8_t CDCready[InterfacesAmount];
void break_handler(uint8_t ifno);
void clstate_handler(uint8_t ifno, uint16_t val);
void linecoding_handler(uint8_t ifno, usb_LineCoding *lc);
// as ugly CDC have no BREAK after disconnected client in non-canonical mode, we should use timeout - more than 2ms
#define DISCONN_TMOUT (1000)
#define DISCONN_TMOUT (2)
// sizes of ringbuffers for outgoing and incoming data
#define RBOUTSZ (512)
@@ -69,9 +69,11 @@ void linecoding_handler(uint8_t ifno, usb_LineCoding *lc);
#define DBGs(s)
#endif
int USB_sendbufspace(uint8_t ifno);
int USB_sendall(uint8_t ifno);
int USB_send(uint8_t ifno, const uint8_t *buf, int len);
int USB_putbyte(uint8_t ifno, uint8_t byte);
int USB_sendstr(uint8_t ifno, const char *string);
int USB_rcvlen(uint8_t ifno);
int USB_receive(uint8_t ifno, uint8_t *buf, int len);
int USB_receivestr(uint8_t ifno, char *buf, int len);

View File

@@ -20,15 +20,10 @@
#include "usb_descr.h"
#include "usb_dev.h"
#undef DBG
#define DBG(x)
#undef DBGs
#define DBGs(x)
static ep_t endpoints[STM32ENDPOINTS];
static uint16_t USB_Addr = 0;
static uint8_t setupdatabuf[EP0DATABUF_SIZE];
static uint8_t setupdatabuf[EP0DATABUF_SIZE] __attribute__((aligned(4)));
static config_pack_t *setup_packet = (config_pack_t*) setupdatabuf;
volatile uint8_t usbON = 0; // device is configured and active
@@ -37,20 +32,15 @@ static inline void std_d2h_req(){
uint16_t st = 0;
switch(setup_packet->bRequest){
case GET_DESCRIPTOR:
DBG("GET_DESCRIPTOR");
get_descriptor(setup_packet);
break;
case GET_STATUS:
DBG("GET_STATUS");
EP_WriteIRQ(0, (uint8_t *)&st, 2); // send status: Bus Powered
break;
case GET_CONFIGURATION:
DBG("GET_CONFIGURATION");
EP_WriteIRQ(0, (uint8_t*)&configuration, 1);
break;
default:
DBG("Wrong");
DBGs(uhex2str(setup_packet->bRequest));
EP_WriteIRQ(0, NULL, 0);
break;
}
@@ -59,21 +49,16 @@ static inline void std_d2h_req(){
static inline void std_h2d_req(){
switch(setup_packet->bRequest){
case SET_ADDRESS:
DBG("SET_ADDRESS");
// new address will be assigned later - after acknowlegement or request to host
USB_Addr = setup_packet->wValue;
DBGs(uhex2str(USB_Addr));
break;
case SET_CONFIGURATION:
DBG("SET_CONFIGURATION");
// Now device configured
configuration = setup_packet->wValue;
set_configuration();
usbON = 1;
break;
default:
DBG("Wrong");
DBGs(uhex2str(setup_packet->bRequest));
break;
}
}
@@ -83,7 +68,6 @@ void WEAK usb_standard_request(){
uint8_t dev2host = (setup_packet->bmRequestType & 0x80) ? 1 : 0;
switch(recipient){
case REQ_RECIPIENT_DEVICE:
DBG("REQ_RECIPIENT_DEVICE");
if(dev2host){
std_d2h_req();
}else{
@@ -91,49 +75,34 @@ void WEAK usb_standard_request(){
}
break;
case REQ_RECIPIENT_INTERFACE:
DBG("REQ_RECIPIENT_INTERFACE");
if(dev2host && setup_packet->bRequest == GET_DESCRIPTOR){
get_descriptor(setup_packet);
}
break;
case REQ_RECIPIENT_ENDPOINT:
DBG("REQ_RECIPIENT_ENDPOINT");
if(setup_packet->bRequest == CLEAR_FEATURE){
}else{
DBG("Wrong");
}
}else{ /* wrong */ }
break;
default:
DBG("Wrong");
DBGs(uhex2str(recipient));
break;
}
if(!dev2host) EP_WriteIRQ(0, NULL, 0);
}
void WEAK usb_class_request(config_pack_t *req, uint8_t _U_ *data, uint16_t _U_ datalen){
DBG("REQ_TYPE_CLASS");
switch(req->bRequest){
case GET_INTERFACE:
DBG("GI");
break;
case SET_CONFIGURATION: // set featuring by req->wValue
DBG("SC");
break;
default:
DBG("Wrong");
DBGs(uhex2str(req->bmRequestType));
DBGs(uhex2str(req->bRequest));
DBGs(uhex2str(req->wIndex));
DBGs(uhex2str(req->wLength));
DBGs(uhex2str(req->wValue));
break;
}
if(0 == (setup_packet->bmRequestType & 0x80)) // host2dev
EP_WriteIRQ(0, NULL, 0);
}
void WEAK usb_vendor_request(config_pack_t _U_ *packet, uint8_t _U_ *data, uint16_t _U_ datalen){
DBG("vendor");
if(0 == (setup_packet->bmRequestType & 0x80)) // host2dev
EP_WriteIRQ(0, NULL, 0);
}
@@ -149,18 +118,16 @@ bmRequestType: 76543210
*/
static void EP0_Handler(){
uint8_t ep0dbuflen = 0;
uint8_t ep0databuf[EP0DATABUF_SIZE];
uint8_t ep0databuf[EP0DATABUF_SIZE] __attribute__((aligned(4)));
uint16_t epstatus = KEEP_DTOG(USB->EPnR[0]); // EP0R on input -> return this value after modifications
int rxflag = RX_FLAG(epstatus);
if(rxflag){ DBG("EP0_Handler"); }
// check direction
if(USB->ISTR & USB_ISTR_DIR){ // OUT interrupt - receive data, CTR_RX==1 (if CTR_TX == 1 - two pending transactions: receive following by transmit)
if(epstatus & USB_EPnR_SETUP){ // setup packet -> copy data to conf_pack
DBG("USB_EPnR_SETUP");
EP_Read(0, setupdatabuf);
// interrupt handler will be called later
}else if(epstatus & USB_EPnR_CTR_RX){ // data packet -> push received data to ep0databuf
if(endpoints[0].rx_cnt){ DBG("data"); DBGs(uhex2str(endpoints[0].rx_cnt));}
//if(endpoints[0].rx_cnt){ }
ep0dbuflen = EP_Read(0, ep0databuf);
}
}
@@ -169,23 +136,16 @@ static void EP0_Handler(){
switch(reqtype){
case REQ_TYPE_STANDARD:
if(SETUP_FLAG(epstatus)){
DBG("REQ_TYPE_STANDARD");
usb_standard_request();
}else{
DBG("REQ_TYPE_STANDARD without SETUP_FLAG");
}
}else{ }
break;
case REQ_TYPE_CLASS:
DBG("REQ_TYPE_CLASS");
usb_class_request(setup_packet, ep0databuf, ep0dbuflen);
break;
case REQ_TYPE_VENDOR:
DBG("REQ_TYPE_VENDOR");
usb_vendor_request(setup_packet, ep0databuf, ep0dbuflen);
break;
default:
DBG("Wrong");
DBGs(uhex2str(reqtype));
EP_WriteIRQ(0, NULL, 0);
break;
}
@@ -195,8 +155,6 @@ static void EP0_Handler(){
if ((USB->DADDR & USB_DADDR_ADD) != USB_Addr){
USB->DADDR = USB_DADDR_EF | USB_Addr;
usbON = 0;
DBG("Enum");
DBGs(uhex2str(USB_Addr));
}
}
//epstatus = KEEP_DTOG(USB->EPnR[0]);
@@ -214,9 +172,14 @@ static void EP0_Handler(){
*/
void EP_WriteIRQ(uint8_t number, const uint8_t *buf, uint16_t size){
if(size > endpoints[number].txbufsz) size = endpoints[number].txbufsz;
#ifndef USB32
uint16_t N2 = (size + 1) >> 1;
// the buffer is 16-bit, so we should copy data as it would be uint16_t
uint16_t *buf16 = (uint16_t *)buf;
#else
int N4 = (size + 3) >> 2;
uint32_t *buf32 = (uint32_t *)buf;
#endif
#if defined USB1_16
// very bad: what if `size` is odd?
uint32_t *out = (uint32_t *)endpoints[number].tx_buf;
@@ -225,13 +188,19 @@ void EP_WriteIRQ(uint8_t number, const uint8_t *buf, uint16_t size){
}
#elif defined USB2_16
// use memcpy instead?
for(int i = 0; i < N2; i++){
for(int i = 0; i < N2; ++i){
endpoints[number].tx_buf[i] = buf16[i];
}
#elif defined USB32
for(int i = 0; i < N4; ++i) endpoints[number].tx_buf[i] = buf32[i];
#else
#error "Define USB1_16 or USB2_16"
#error "Define USB1_16 / USB2_16 / USB32"
#endif
#ifndef USB32
USB_BTABLE->EP[number].USB_COUNT_TX = size;
#else
USB_BTABLE->EP[number].USB_ADDR_COUNT_TX = (USB_BTABLE->EP[number].USB_ADDR_COUNT_TX & 0xffff) | (size << 16);
#endif
}
/**
@@ -266,8 +235,12 @@ int EP_Read(uint8_t number, uint8_t *buf){
// use memcpy instead?
for(int i = 0; i < sz; ++i)
buf[i] = endpoints[number].rx_buf[i];
#elif defined USB32
uint32_t *u32buf = (uint32_t*) buf;
int N4 = (sz + 3) >> 2;
for(int i = 0; i < N4; ++i) u32buf[i] = endpoints[number].rx_buf[i];
#else
#error "Define USB1_16 or USB2_16"
#error "Define USB1_16 / USB2_16 / USB32"
#endif
return sz;
}
@@ -284,11 +257,16 @@ static uint16_t lastaddr = LASTADDR_DEFAULT;
* @return 0 if all OK
*/
int EP_Init(uint8_t number, uint8_t type, uint16_t txsz, uint16_t rxsz, void (*func)(ep_t ep)){
#ifdef STM32G0
// in STM32G0 all buffers should be aligned by 32 bits
if(txsz & 3) txsz = ((txsz >> 2)+1) << 2;
if(rxsz & 3) rxsz = ((rxsz >> 2)+1) << 2;
#endif
if(number >= STM32ENDPOINTS) return 4; // out of configured amount
if(txsz > USB_BTABLE_SIZE/ACCESSZ || rxsz > USB_BTABLE_SIZE/ACCESSZ) return 1; // buffer too large
if(lastaddr + txsz + rxsz >= USB_BTABLE_SIZE/ACCESSZ) return 2; // out of btable
USB->EPnR[number] = (type << 9) | (number & USB_EPnR_EA);
USB->EPnR[number] ^= USB_EPnR_STAT_RX | USB_EPnR_STAT_TX_1;
USB->EPnR[number] ^= USB_EPnR_STAT_RX | USB_EPnR_STAT_TX;
if(rxsz & 1) return 3; // wrong rx buffer size
uint16_t countrx = 0;
if(rxsz < 64) countrx = rxsz / 2;
@@ -296,25 +274,49 @@ int EP_Init(uint8_t number, uint8_t type, uint16_t txsz, uint16_t rxsz, void (*f
if(rxsz & 0x1f) return 3; // should be multiple of 32
countrx = 31 + rxsz / 32;
}
USB_BTABLE->EP[number].USB_ADDR_TX = lastaddr;
#ifdef USB32
endpoints[number].tx_buf = (uint32_t *)(USB_BTABLE_BASE + lastaddr * ACCESSZ);
#else
endpoints[number].tx_buf = (uint16_t *)(USB_BTABLE_BASE + lastaddr * ACCESSZ);
#endif
endpoints[number].txbufsz = txsz;
lastaddr += txsz;
#ifdef USB32
USB_BTABLE->EP[number].USB_ADDR_COUNT_TX = (uint32_t) lastaddr;
#else
USB_BTABLE->EP[number].USB_ADDR_TX = lastaddr;
USB_BTABLE->EP[number].USB_COUNT_TX = 0;
USB_BTABLE->EP[number].USB_ADDR_RX = lastaddr;
#endif
lastaddr += txsz;
#ifdef USB32
endpoints[number].rx_buf = (uint32_t *)(USB_BTABLE_BASE + lastaddr * ACCESSZ);
USB_BTABLE->EP[number].USB_ADDR_COUNT_RX = (uint32_t) lastaddr | countrx << 26;
#else
endpoints[number].rx_buf = (uint8_t *)(USB_BTABLE_BASE + lastaddr * ACCESSZ);
lastaddr += rxsz;
USB_BTABLE->EP[number].USB_ADDR_RX = lastaddr;
USB_BTABLE->EP[number].USB_COUNT_RX = countrx << 10;
#endif
lastaddr += rxsz;
endpoints[number].func = func;
return 0;
}
// refresh EP after interface reconnected
void EP_reset(uint8_t epno){
if(epno >= STM32ENDPOINTS) return;
// keep DTOGs (don't write 1 to them), clear CTR (write 0 to them)
// and set STAT to VALID (write 1 where was 0)
uint16_t epstatus = KEEP_DTOG(USB->EPnR[epno]);
USB->EPnR[epno] = (epstatus & ~(USB_EPnR_CTR_TX|USB_EPnR_CTR_RX)) ^
(USB_EPnR_STAT_RX | USB_EPnR_STAT_TX);
USB_BTABLE->EP[epno].USB_COUNT_TX = 0;
}
// standard IRQ handler
void USB_IRQ(){
uint32_t CNTR = USB->CNTR;
USB->CNTR = 0;
if(USB->ISTR & USB_ISTR_RESET){
DBG("USB_ISTR_RESET");
uint32_t istr = USB->ISTR;
if(istr & USB_ISTR_RESET){
usbON = 0;
// Reinit registers
CNTR = USB_CNTR_RESETM | USB_CNTR_CTRM | USB_CNTR_SUSPM;
@@ -323,45 +325,56 @@ void USB_IRQ(){
lastaddr = LASTADDR_DEFAULT;
// clear address, leave only enable bit
USB->DADDR = USB_DADDR_EF;
USB->ISTR = ~USB_ISTR_RESET;
//USB->ISTR = ~(USB_ISTR_RESET); // clear all flags
if(EP_Init(0, EP_TYPE_CONTROL, USB_EP0BUFSZ, USB_EP0BUFSZ, EP0_Handler)){
DBG("Can't init EP0");
return;
};
}
if(USB->ISTR & USB_ISTR_CTR){
if(istr & USB_ISTR_CTR){
// EP number
uint8_t n = USB->ISTR & USB_ISTR_EPID;
uint8_t n = istr & USB_ISTR_EPID;
if (istr & USB_ISTR_DIR){ // OUT
}else{ // IN
}
// copy received bytes amount
endpoints[n].rx_cnt = USB_BTABLE->EP[n].USB_COUNT_RX & 0x3FF; // low 10 bits is counter
endpoints[n].rx_cnt =
#ifdef USB32
(USB_BTABLE->EP[n].USB_ADDR_COUNT_RX >> 16) & 0x3FF;
#else
USB_BTABLE->EP[n].USB_COUNT_RX & 0x3FF; // low 10 bits is counter
#endif
// call EP handler
if(endpoints[n].func) endpoints[n].func();
}
if(USB->ISTR & USB_ISTR_WKUP){ // wakeup
DBG("USB_ISTR_WKUP");
#ifndef STM32F0
CNTR &= ~(USB_CNTR_FSUSP | USB_CNTR_LP_MODE | USB_CNTR_WKUPM); // clear suspend flags
#else
if(istr & USB_ISTR_WKUP){ // wakeup
#if defined STM32F0
CNTR &= ~(USB_CNTR_FSUSP | USB_CNTR_LPMODE | USB_CNTR_WKUPM);
#endif
USB->ISTR = ~USB_ISTR_WKUP;
}
if(USB->ISTR & USB_ISTR_SUSP){ // suspend -> still no connection, may sleep
DBG("USB_ISTR_SUSP");
usbON = 0;
#ifndef STM32F0
CNTR |= USB_CNTR_FSUSP | USB_CNTR_LP_MODE | USB_CNTR_WKUPM;
#elif defined STM32G0
CNTR &= ~(USB_CNTR_SUSPEN | USB_CNTR_PDWN | USB_CNTR_WKUPM);
#else
CNTR &= ~(USB_CNTR_FSUSP | USB_CNTR_LP_MODE | USB_CNTR_WKUPM); // clear suspend flags
#endif
//USB->ISTR = ~USB_ISTR_WKUP;
}
if(istr & USB_ISTR_SUSP){ // suspend -> still no connection, may sleep
usbON = 0;
#if defined STM32F0
CNTR |= USB_CNTR_FSUSP | USB_CNTR_LPMODE | USB_CNTR_WKUPM;
#elif defined STM32G0
CNTR |= USB_CNTR_SUSPEN | USB_CNTR_WKUPM;
#else
CNTR |= USB_CNTR_FSUSP | USB_CNTR_LP_MODE | USB_CNTR_WKUPM;
#endif
CNTR &= ~(USB_CNTR_SUSPM);
USB->ISTR = ~USB_ISTR_SUSP;
//USB->ISTR = ~USB_ISTR_SUSP;
}
USB->ISTR = 0; // clear all flags
USB->CNTR = CNTR; // rewoke interrupts
}
// here we suppose that all PIN settings done in hw_setup earlier
void USB_setup(){
lastaddr = LASTADDR_DEFAULT; // clear last address settings
#if defined STM32F3
NVIC_DisableIRQ(USB_LP_IRQn);
// remap USB LP & Wakeup interrupts to 75 and 76 - works only on pure F303
@@ -371,6 +384,7 @@ void USB_setup(){
NVIC_DisableIRQ(USB_LP_CAN1_RX0_IRQn);
NVIC_DisableIRQ(USB_HP_CAN1_TX_IRQn);
#elif defined STM32F0
// All is clocking from HSI48
NVIC_DisableIRQ(USB_IRQn);
RCC->APB1ENR |= RCC_APB1ENR_CRSEN;
RCC->CFGR3 &= ~RCC_CFGR3_USBSW; // reset USB
@@ -383,16 +397,33 @@ void USB_setup(){
CRS->CR |= CRS_CR_AUTOTRIMEN; // enable auto trim
CRS->CR |= CRS_CR_CEN; // enable freq counter & block CRS->CFGR as read-only
RCC->CFGR |= RCC_CFGR_SW;
#elif defined STM32G0
NVIC_DisableIRQ(USB_UCPD1_2_IRQn);
PWR->CR2 |= PWR_CR2_USV; // enable USB powering
//RCC->APBENR2 |= RCC_APBENR2_SYSCFGEN; // enable tacting of SYSCFG
// independent clocking of USB from HSI48
RCC->CR |= RCC_CR_HSI48ON;
uint32_t tmout = 16000000;
while(!(RCC->CR & RCC_CR_HSI48RDY)) if(--tmout == 0) break;
RCC->CCIPR2 &= ~RCC_CCIPR2_USBSEL; // select HSI48 for USB
RCC->APBENR1 |= RCC_APBENR1_CRSEN; // CRS clocking
CRS->CFGR = (31LL << CRS_CFGR_FELIM_Pos) | // tolerance (usually 31)
(48000LL / 1LL - 1LL) << CRS_CFGR_RELOAD_Pos | // 48MHz / 1kHZ (SOF)
CRS_CFGR_SYNCSRC_1; // USB SOF as sync source (0x2)
CRS->CR |= CRS_CR_AUTOTRIMEN | CRS_CR_CEN; // Enable autotrim and turn on Clock Recovery System
RCC->APBENR1 |= RCC_APBENR1_USBEN;
#endif
#ifndef STM32G0
RCC->APB1ENR |= RCC_APB1ENR_USBEN;
//??
USB->CNTR = USB_CNTR_FRES; // Force USB Reset
for(uint32_t ctr = 0; ctr < 72000; ++ctr) nop(); // wait >1ms
USB->CNTR = 0;
USB->BTABLE = 0;
#else
USB->CNTR = USB_CNTR_USBRST;
#endif
for(uint32_t ctr = 0; ctr < 72000; ++ctr) nop(); // wait >1ms
USB->CNTR = USB_CNTR_RESETM; // allow only reset interrupts
USB->DADDR = 0;
USB->ISTR = 0;
USB->CNTR = USB_CNTR_RESETM; // allow only reset interrupts
#if defined STM32F3
NVIC_EnableIRQ(USB_LP_IRQn);
#elif defined STM32F1
@@ -400,8 +431,11 @@ void USB_setup(){
#elif defined STM32F0
USB->BCDR |= USB_BCDR_DPPU;
NVIC_EnableIRQ(USB_IRQn);
#elif defined STM32G0
USB->BCDR |= USB_BCDR_DPPU; // turn ON DP pullup
NVIC_EnableIRQ(USB_UCPD1_2_IRQn);
#endif
setup_interfaces();
setup_interfaces(); // refresh interfaces' names
}
@@ -411,4 +445,6 @@ void usb_lp_isr() __attribute__ ((alias ("USB_IRQ")));
void usb_lp_can_rx0_isr() __attribute__ ((alias ("USB_IRQ")));
#elif defined STM32F0
void usb_isr() __attribute__ ((alias ("USB_IRQ")));
#elif defined STM32G0
void usb_ucpd1_2_isr() __attribute__ ((alias ("USB_IRQ")));
#endif

View File

@@ -19,6 +19,10 @@
#include <stdint.h>
#include <wchar.h>
#ifndef _U_
#define _U_ __attribute__((unused))
#endif
/******************************************************************
* Hardware registers etc *
*****************************************************************/
@@ -30,6 +34,8 @@
#define USB_BASE ((uint32_t)0x40005C00)
#elif defined STM32F3
#include <stm32f3.h>
#elif defined STM32G0
#include <stm32g0.h>
#endif
// max endpoints number
@@ -38,14 +44,16 @@
* Buffers size definition
**/
// F0 - USB2_16; F1 - USB1_16; F3 - 1/2 depending on series
#if !defined USB1_16 && !defined USB2_16
// F0 - USB2_16; F1 - USB1_16; F3 - 1/2 depending on series; G0 - USB32
#if !defined USB1_16 && !defined USB2_16 && !defined USB32
#if defined STM32F0
#define USB2_16
#elif defined STM32F1
#define USB1_16
#elif defined STM32G0
#define USB32
#else
#error "Can't determine USB1_16 or USB2_16, define by hands"
#error "Can't determine USB1_16/USB2_16/USB32, define by hands"
#endif
#endif
@@ -59,8 +67,8 @@
#if defined STM32F0
#define USB_BTABLE_SIZE 1024
#elif defined STM32F3
#define USB_BTABLE_SIZE 512
#warning "Please, check real buffer size due to docs"
#define USB_BTABLE_SIZE 1024
//#warning "Please, check real buffer size due to docs"
#else
#error "define STM32F0 or STM32F3"
#endif
@@ -68,16 +76,21 @@
#if defined STM32F0
#define USB_BTABLE_SIZE 768
#elif defined STM32F3
#define USB_BTABLE_SIZE 512
#warning "Please, check real buffer size due to docs"
#define USB_BTABLE_SIZE 768
#elif defined STM32G0
#define USB_BTABLE_SIZE 2048
//#warning "Please, check real buffer size due to docs"
#else // STM32F103: 1024 bytes but with 32-bit addressing
#define USB_BTABLE_SIZE 1024
#endif
#endif // NOCAN
// first 64 bytes of USB_BTABLE are registers!
#ifndef STM32G0
#define USB_BTABLE_BASE 0x40006000
#else
#define USB_BTABLE_BASE 0x40009800
#endif
#define USB ((USB_TypeDef *) USB_BASE)
#ifdef USB_BTABLE
@@ -120,8 +133,12 @@ typedef struct {
__IO uint32_t ISTR;
__IO uint32_t FNR;
__IO uint32_t DADDR;
#ifndef USB32
__IO uint32_t BTABLE;
#ifdef STM32F0
#else
__IO uint32_t RESERVED1; // there's no BTABLE register in STM32G0
#endif
#if defined STM32F0 || defined USB32
__IO uint32_t LPMCSR;
__IO uint32_t BCDR;
#endif
@@ -135,16 +152,19 @@ typedef struct{
__IO uint16_t USB_ADDR_RX;
__IO uint16_t USB_COUNT_RX;
#define ACCESSZ (1)
#define BUFTYPE uint8_t
#elif defined USB1_16
__IO uint32_t USB_ADDR_TX;
__IO uint32_t USB_COUNT_TX;
__IO uint32_t USB_ADDR_RX;
__IO uint32_t USB_COUNT_RX;
#define ACCESSZ (2)
#define BUFTYPE uint16_t
#elif defined USB32
// 32-bit registers: addr & count in one!
__IO uint32_t USB_ADDR_COUNT_TX;
__IO uint32_t USB_ADDR_COUNT_RX;
#define ACCESSZ (1)
#else
#error "Define USB1_16 or USB2_16"
#error "Define USB1_16 (16 bits over 32bit register), USB2_16 (16 bits over 16 bit register) or USB32 (32 bist over 32 bit register)"
#endif
} USB_EPDATA_TypeDef;
@@ -303,9 +323,17 @@ typedef struct {
// endpoints state
typedef struct{
#ifdef USB32
uint32_t *tx_buf; // transmission buffer address
#else
uint16_t *tx_buf; // transmission buffer address
#endif
uint16_t txbufsz; // transmission buffer size
#ifdef USB32
uint32_t *rx_buf; // reception buffer address
#else
uint8_t *rx_buf; // reception buffer address
#endif
void (*func)(); // endpoint action function
unsigned rx_cnt : 10; // received data counter
} ep_t;
@@ -317,6 +345,7 @@ int EP_Init(uint8_t number, uint8_t type, uint16_t txsz, uint16_t rxsz, void (*f
void EP_WriteIRQ(uint8_t number, const uint8_t *buf, uint16_t size);
void EP_Write(uint8_t number, const uint8_t *buf, uint16_t size);
int EP_Read(uint8_t number, uint8_t *buf);
void EP_reset(uint8_t epno);
// could be [re]defined in usb_dev.c
extern void usb_class_request(config_pack_t *packet, uint8_t *data, uint16_t datalen);

View File

@@ -1,2 +1,2 @@
#define BUILD_NUMBER "144"
#define BUILD_DATE "2026-02-13"
#define BUILD_NUMBER "147"
#define BUILD_DATE "2026-03-23"

View File

@@ -21,6 +21,7 @@
#include "hardware.h"
#include "proto.h"
#include "spi.h"
#include "strfunc.h"
#include "usb_dev.h"
#define CU(a) ((const uint8_t*)a)
@@ -171,6 +172,7 @@ void canon_init(){
*/
void canon_proc(){
static uint32_t Tconn = 0;
if(state == LENS_DISABLED) return;
if(state == LENS_DISCONNECTED){
if(!LENSCONNECTED()){
Tconn = 0;
@@ -315,7 +317,7 @@ void canon_proc(){
/*
case CANON_GETFOCM: // don't work @EF200
uval = (rbuf[1] << 24) | (rbuf[2] << 16) | (rbuf[3] << 8) | rbuf[4];
USB_send("Fval="); USB_send(u2str(uval)); USB_send("\n");
USB_sendstr("Fval="); USB_sendstr(u2str(uval)); USB_sendstr("\n");
break;
*/
}
@@ -393,7 +395,7 @@ int canon_asku16(uint8_t cmd){
int canon_getinfo(){
if(!ready || !canon_read(CANON_GETINFO, 6)) return 1;
USB_sendstr("Info="); for(int i = 1; i < 7; ++i){
USB_sendstr(u2hexstr(buf[i])); USB_sendstr(" ");
USB_sendstr(uhex2str(buf[i])); USB_sendstr(" ");
}
//canon_poll();
USB_sendstr("\n");
@@ -403,3 +405,25 @@ int canon_getinfo(){
uint16_t canon_getstate(){
return state | (inistate << 8);
}
void canon_disable(){
if(state == LENS_DISABLED) return;
state = LENS_DISABLED;
LENS_OFF();
ready = 0;
}
void canon_enable(){
if(state != LENS_DISABLED) return;
if(OVERCURRENT()){
state = LENS_OVERCURRENT;
return;
}
if(!LENSCONNECTED()){
state = LENS_DISCONNECTED;
return;
}
LENS_ON();
state = LENS_SLEEPING;
ready = 1;
}

View File

@@ -18,7 +18,7 @@
#pragma once
#include <stm32f1.h>
#include <stdint.h>
// all data sent in big-endian format
@@ -92,6 +92,7 @@ typedef enum{
LENS_INITIALIZED, // initializing process
LENS_READY, // ready to operate
LENS_ERR, // some error occured - reconnect after REINIT_PAUSE
LENS_DISABLED, // powered off by command
LENS_S_AMOUNT
} lens_state;
@@ -109,6 +110,8 @@ typedef enum{
void canon_init();
void canon_proc();
void canon_disable();
void canon_enable();
int canon_diaphragm(char command);
int canon_focus(int16_t val);
int canon_sendcmd(uint8_t cmd);

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 15.0.1, 2025-03-11T16:16:01. -->
<!-- Written by QtCreator 18.0.0, 2026-04-14T09:27:26. -->
<qtcreator>
<data>
<variable>EnvironmentId</variable>
@@ -13,8 +13,8 @@
<data>
<variable>ProjectExplorer.Project.EditorSettings</variable>
<valuemap type="QVariantMap">
<value type="bool" key="EditorConfiguration.AutoDetect">true</value>
<value type="bool" key="EditorConfiguration.AutoIndent">true</value>
<value type="bool" key="EditorConfiguration.AutoSpacesForTabs">false</value>
<value type="bool" key="EditorConfiguration.CamelCaseNavigation">true</value>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.0">
<value type="QString" key="language">Cpp</value>
@@ -86,12 +86,14 @@
<valuelist type="QVariantList" key="ClangTools.SuppressedDiagnostics"/>
<value type="bool" key="ClangTools.UseGlobalSettings">true</value>
</valuemap>
<value type="int" key="RcSync">0</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.Target.0</variable>
<valuemap type="QVariantMap">
<value type="QString" key="DeviceType">Desktop</value>
<value type="bool" key="HasPerBcDcs">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{91347f2c-5221-46a7-80b1-0a054ca02f79}</value>
@@ -109,8 +111,8 @@
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericMakeStep</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Сборка</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Сборка</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
@@ -122,8 +124,8 @@
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericMakeStep</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Очистка</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Очистка</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
@@ -133,13 +135,46 @@
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">По умолчанию</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericBuildConfiguration</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
<value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
<valuelist type="QVariantList" key="CustomOutputParsers"/>
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
<value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
<value type="QString" key="PerfRecordArgsId">-e cpu-cycles --call-graph dwarf,4096 -F 250</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.BuildConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Развёртывание</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Развёртывание</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
@@ -161,6 +196,7 @@
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
</valuemap>
@@ -171,10 +207,6 @@
<variable>ProjectExplorer.Project.TargetCount</variable>
<value type="qlonglong">1</value>
</data>
<data>
<variable>ProjectExplorer.Project.Updater.FileVersion</variable>
<value type="int">22</value>
</data>
<data>
<variable>Version</variable>
<value type="int">22</value>

View File

@@ -13,6 +13,8 @@ ringbuffer.c
ringbuffer.h
spi.c
spi.h
strfunc.c
strfunc.h
usb.c
usb.h
usb_descr.c

View File

@@ -25,6 +25,7 @@
#include "flash.h"
#include "proto.h"
#include "strfunc.h"
#include "usb_dev.h" // printout
#include <string.h> // memcpy
@@ -145,7 +146,7 @@ static int write2flash(const void *start, const void *wrdata, uint32_t stor_size
}
#ifdef EBUG
USB_sendstr(u2str(stor_size)); USB_sendstr("bytes stored @0x");
USB_sendstr(u2hexstr((uint32_t)(address + i))); USB_sendstr("\n");
USB_sendstr(uhex2str((uint32_t)(address + i))); USB_sendstr("\n");
#endif
FLASH->SR = FLASH_SR_EOP | FLASH_SR_PGERR | FLASH_SR_WRPRTERR;
}

View File

@@ -48,14 +48,13 @@ int main(void){
uint32_t SPIctr = Tms;
while(1){
// TODO: add CAN bus parsing
IWDG->KR = IWDG_REFRESH;
int l = USB_receivestr(inbuff, 255);
if(l > 0){
parse_cmd(inbuff); // call it even for NULL (if `flood` is running)
if(Tms - SPIctr > CANONPROC_INTERVAL){ // not more than once per 10ms
SPIctr = Tms;
canon_proc();
}
if(Tms - SPIctr > CANONPROC_INTERVAL){ // not more than once per 10ms
SPIctr = Tms;
canon_proc();
}
int l = USB_receivestr(inbuff, 255);
if(l > 0) parse_cmd(inbuff); // call it even for NULL (if `flood` is running)
}
}

View File

@@ -23,143 +23,16 @@
#include "hardware.h"
#include "proto.h"
#include "spi.h"
#include "strfunc.h"
#include "usb_dev.h"
#include "version.inc"
static const char *OK = "OK", *FAIL = "FAIL";
char *omit_spaces(const char *buf){
while(*buf){
if(*buf > ' ') break;
++buf;
}
return (char*)buf;
}
// In case of overflow return `buf` and N==0xffffffff
// read decimal number & return pointer to next non-number symbol
static char *getdec(const char *buf, uint32_t *N){
char *start = (char*)buf;
uint32_t num = 0;
while(*buf){
char c = *buf;
if(c < '0' || c > '9'){
break;
}
if(num > 429496729 || (num == 429496729 && c > '5')){ // overflow
*N = 0xffffff;
return start;
}
num *= 10;
num += c - '0';
++buf;
}
*N = num;
return (char*)buf;
}
// read hexadecimal number (without 0x prefix!)
static char *gethex(const char *buf, uint32_t *N){
char *start = (char*)buf;
uint32_t num = 0;
while(*buf){
char c = *buf;
uint8_t M = 0;
if(c >= '0' && c <= '9'){
M = '0';
}else if(c >= 'A' && c <= 'F'){
M = 'A' - 10;
}else if(c >= 'a' && c <= 'f'){
M = 'a' - 10;
}
if(M){
if(num & 0xf0000000){ // overflow
*N = 0xffffff;
return start;
}
num <<= 4;
num += c - M;
}else{
break;
}
++buf;
}
*N = num;
return (char*)buf;
}
// read octal number (without 0 prefix!)
static char *getoct(const char *buf, uint32_t *N){
char *start = (char*)buf;
uint32_t num = 0;
while(*buf){
char c = *buf;
if(c < '0' || c > '7'){
break;
}
if(num & 0xe0000000){ // overflow
*N = 0xffffff;
return start;
}
num <<= 3;
num += c - '0';
++buf;
}
*N = num;
return (char*)buf;
}
// read binary number (without b prefix!)
static char *getbin(const char *buf, uint32_t *N){
char *start = (char*)buf;
uint32_t num = 0;
while(*buf){
char c = *buf;
if(c < '0' || c > '1'){
break;
}
if(num & 0x80000000){ // overflow
*N = 0xffffff;
return start;
}
num <<= 1;
if(c == '1') num |= 1;
++buf;
}
*N = num;
return (char*)buf;
}
/**
* @brief getnum - read uint32_t from string (dec, hex or bin: 127, 0x7f, 0b1111111)
* @param buf - buffer with number and so on
* @param N - the number read
* @return pointer to first non-number symbol in buf
* (if it is == buf, there's no number or if *N==0xffffffff there was overflow)
*/
char *getnum(const char *txt, uint32_t *N){
char *nxt = NULL;
char *s = omit_spaces(txt);
if(*s == '0'){ // hex, oct or 0
if(s[1] == 'x' || s[1] == 'X'){ // hex
nxt = gethex(s+2, N);
if(nxt == s+2) nxt = (char*)txt;
}else if(s[1] > '0'-1 && s[1] < '8'){ // oct
nxt = getoct(s+1, N);
if(nxt == s+1) nxt = (char*)txt;
}else{ // 0
nxt = s+1;
*N = 0;
}
}else if(*s == 'b' || *s == 'B'){
nxt = getbin(s+1, N);
if(nxt == s+1) nxt = (char*)txt;
}else{
nxt = getdec(s, N);
if(nxt == s) nxt = (char*)txt;
}
return nxt;
}
const char* helpmsg =
"https://github.com/eddyem/stm32samples/tree/master/F1-nolib/Canon_managing_device build#" BUILD_NUMBER " @ " BUILD_DATE "\n"
"# - turn off lens power\n"
"* - turn on lens power\n"
"0 - move to smallest foc value (e.g. 2.5m)\n"
"1 - move to largest foc value (e.g. infinity)\n"
"a - move focus to given ABSOLUTE position or get current value (without number)\n"
@@ -213,7 +86,7 @@ static void sendspibuf(){
USB_sendstr("Got SPI answer: ");
for(int i = 0; i < spibuflen; ++i){
if(i) USB_sendstr(", ");
USB_sendstr(u2hexstr(buf[i]));
USB_sendstr(uhex2str(buf[i]));
}
USB_sendstr("\n");
}else USB_sendstr("Failed to send SPI buffer\n");
@@ -234,6 +107,7 @@ const char *connmsgs[LENS_S_AMOUNT+1] = {
[LENS_INITIALIZED] = "initialized",
[LENS_READY] = "ready",
[LENS_ERR] = "error",
[LENS_DISABLED] = "turned off",
[LENS_S_AMOUNT] = "wrong state"
};
const char *inimsgs[INI_S_AMOUNT+1] = {
@@ -257,6 +131,14 @@ void parse_cmd(const char *buf){
lastFloodTime= FALSE;
if(buf[1] == '\n' || !buf[1]){ // one symbol commands
switch(*buf){
case '*':
canon_enable();
USB_sendstr(OK);
break;
case '#':
canon_disable();
USB_sendstr(OK);
break;
case 'a':
case 'f':
errw(canon_focus(-1));
@@ -284,7 +166,7 @@ void parse_cmd(const char *buf){
USB_sendstr(OK);
break;
case 'F': // just watch SPI->CR1 value
USB_sendstr("SPI1->CR1="); USB_sendstr(u2hexstr(SPI_CR1));
USB_sendstr("SPI1->CR1="); USB_sendstr(uhex2str(SPI_CR1));
break;
case 'G':
USB_sendstr("SPI ");
@@ -344,7 +226,7 @@ void parse_cmd(const char *buf){
}
uint32_t D = 0;
int16_t neg;
char *nxt;
const char *nxt;
switch(*buf++){ // long messages
case 'a': // move focus to absolute position
buf = omit_spaces(buf);
@@ -425,7 +307,7 @@ void parse_cmd(const char *buf){
USB_sendstr(helpmsg);
return;
}
USB_sendstr("SPI_CR1="); USB_sendstr(u2hexstr(SPI_CR1));
USB_sendstr("SPI_CR1="); USB_sendstr(uhex2str(SPI_CR1));
break;
case 'L':
if(0 == initspibuf(buf)){
@@ -445,7 +327,7 @@ void parse_cmd(const char *buf){
USB_sendstr("Send: ");
for(int i = 0; i < spibuflen; ++i){
if(i) USB_sendstr(", ");
USB_sendstr(u2hexstr(spibuf[i]));
USB_sendstr(uhex2str(spibuf[i]));
}
USB_sendstr("\n");
sendspibuf();
@@ -456,41 +338,3 @@ void parse_cmd(const char *buf){
}
newline();
}
// return string with number `val`
char *u2str(uint32_t val){
static char strbuf[11];
char *bufptr = &strbuf[10];
*bufptr = 0;
if(!val){
*(--bufptr) = '0';
}else{
while(val){
*(--bufptr) = val % 10 + '0';
val /= 10;
}
}
return bufptr;
}
char *u2hexstr(uint32_t val){
static char strbuf[11] = "0x";
char *sptr = strbuf + 2;
uint8_t *ptr = (uint8_t*)&val + 3;
int8_t i, j, z=1;
for(i = 0; i < 4; ++i, --ptr){
if(*ptr == 0){ // omit leading zeros
if(i == 3) z = 0;
if(z) continue;
}
else z = 0;
for(j = 1; j > -1; --j){
uint8_t half = (*ptr >> (4*j)) & 0x0f;
if(half < 10) *sptr++ = half + '0';
else *sptr++ = half - 10 + 'a';
}
}
*sptr = 0;
return strbuf;
}

View File

@@ -17,24 +17,15 @@
*/
#pragma once
#ifndef PROTO_H__
#define PROTO_H__
#include <stm32f1.h>
#define printu(x) do{USB_sendstr(u2str(x));}while(0)
#define printuhex(x) do{USB_sendstr(uhex2str(x));}while(0)
#ifdef EBUG
#define DBG(x) do{USB_send(x); USB_send("\n");}while(0)
#define DBG(x) do{USB_sendstr(x); newline();}while(0)
#else
#define DBG(x)
#endif
void parse_cmd(const char *buf);
char *omit_spaces(const char *buf);
char *getnum(const char *buf, uint32_t *N);
char *u2str(uint32_t val);
char *u2hexstr(uint32_t val);
#endif // PROTO_H__

View File

@@ -15,6 +15,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include <stm32f1.h>
#include "ringbuffer.h"
static int datalen(ringbuffer *b){
@@ -24,7 +26,7 @@ static int datalen(ringbuffer *b){
// stored data length
int RB_datalen(ringbuffer *b){
if(b->busy) return -1;
if(!b || b->busy) return -1;
b->busy = 1;
int l = datalen(b);
b->busy = 0;
@@ -32,7 +34,7 @@ int RB_datalen(ringbuffer *b){
}
static int hasbyte(ringbuffer *b, uint8_t byte){
if(b->head == b->tail) return -1; // no data in buffer
if(!b || b->head == b->tail) return -1; // no data in buffer
int startidx = b->head;
if(b->head > b->tail){ //
for(int found = b->head; found < b->length; ++found)
@@ -51,18 +53,13 @@ static int hasbyte(ringbuffer *b, uint8_t byte){
* @return index if found, -1 if none or busy
*/
int RB_hasbyte(ringbuffer *b, uint8_t byte){
if(b->busy) return -1;
if(!b || b->busy) return -1;
b->busy = 1;
int ret = hasbyte(b, byte);
b->busy = 0;
return ret;
}
// poor memcpy
static void mcpy(uint8_t *targ, const uint8_t *src, int l){
while(l--) *targ++ = *src++;
}
// increment head or tail
TRUE_INLINE void incr(ringbuffer *b, volatile int *what, int n){
*what += n;
@@ -76,9 +73,9 @@ static int read(ringbuffer *b, uint8_t *s, int len){
int _1st = b->length - b->head;
if(_1st > l) _1st = l;
if(_1st > len) _1st = len;
mcpy(s, b->data + b->head, _1st);
memcpy(s, b->data + b->head, _1st);
if(_1st < len && l > _1st){
mcpy(s+_1st, b->data, l - _1st);
memcpy(s+_1st, b->data, l - _1st);
incr(b, &b->head, l);
return l;
}
@@ -94,20 +91,27 @@ static int read(ringbuffer *b, uint8_t *s, int len){
* @return bytes read or -1 if busy
*/
int RB_read(ringbuffer *b, uint8_t *s, int len){
if(b->busy) return -1;
if(!b || b->busy || !s || len < 1) return -1;
b->busy = 1;
int r = read(b, s, len);
b->busy = 0;
return r;
}
static int readto(ringbuffer *b, uint8_t byte, uint8_t *s, int len){
// length of data from current position to `byte` (including byte)
static int lento(ringbuffer *b, uint8_t byte){
int idx = hasbyte(b, byte);
if(idx < 0) return 0;
int partlen = idx + 1 - b->head;
// now calculate length of new data portion
if(idx < b->head) partlen += b->length;
if(partlen > len) return -read(b, s, len);
return partlen;
}
static int readto(ringbuffer *b, uint8_t byte, uint8_t *s, int len){
int partlen = lento(b, byte);
if(!partlen) return 0;
if(partlen > len) return -1;
return read(b, s, partlen);
}
@@ -115,26 +119,41 @@ static int readto(ringbuffer *b, uint8_t byte, uint8_t *s, int len){
* @brief RB_readto fill array `s` with data until byte `byte` (with it)
* @param b - ringbuffer
* @param byte - check byte
* @param s - buffer to write data
* @param len - length of `s`
* @param s - buffer to write data or NULL to clear data
* @param len - length of `s` or 0 to clear data
* @return amount of bytes written (negative, if len<data in buffer or buffer is busy)
*/
int RB_readto(ringbuffer *b, uint8_t byte, uint8_t *s, int len){
if(b->busy) return -1;
if(!b || b->busy) return -1;
b->busy = 1;
int n = readto(b, byte, s, len);
int n = 0;
if(s && len > 0){
n = readto(b, byte, s, len);
}else{
incr(b, &b->head, lento(b, byte)); // just throw data out
}
b->busy = 0;
return n;
}
int RB_datalento(ringbuffer *b, uint8_t byte){
if(!b || b->busy) return -1;
b->busy = 1;
int n = lento(b, byte);
b->busy = 0;
return n;
}
// if l < rest of buffer, truncate and return actually written bytes
static int write(ringbuffer *b, const uint8_t *str, int l){
int r = b->length - 1 - datalen(b); // rest length
if(l > r || !l) return 0;
if(r < 1) return 0;
if(l > r) l = r;
int _1st = b->length - b->tail;
if(_1st > l) _1st = l;
mcpy(b->data + b->tail, str, _1st);
memcpy(b->data + b->tail, str, _1st);
if(_1st < l){ // add another piece from start
mcpy(b->data, str+_1st, l-_1st);
memcpy(b->data, str+_1st, l-_1st);
}
incr(b, &b->tail, l);
return l;
@@ -148,7 +167,7 @@ static int write(ringbuffer *b, const uint8_t *str, int l){
* @return amount of bytes written or -1 if busy
*/
int RB_write(ringbuffer *b, const uint8_t *str, int l){
if(b->busy) return -1;
if(!b || b->busy || !str || l < 1) return -1;
b->busy = 1;
int w = write(b, str, l);
b->busy = 0;
@@ -157,7 +176,7 @@ int RB_write(ringbuffer *b, const uint8_t *str, int l){
// just delete all information in buffer `b`
int RB_clearbuf(ringbuffer *b){
if(b->busy) return -1;
if(!b || b->busy) return -1;
b->busy = 1;
b->head = 0;
b->tail = 0;

View File

@@ -17,13 +17,7 @@
#pragma once
#if defined STM32F0
#include <stm32f0.h>
#elif defined STM32F1
#include <stm32f1.h>
#elif defined STM32F3
#include <stm32f3.h>
#endif
#include <stdint.h>
typedef struct{
uint8_t *data; // data buffer
@@ -38,4 +32,5 @@ int RB_readto(ringbuffer *b, uint8_t byte, uint8_t *s, int len);
int RB_hasbyte(ringbuffer *b, uint8_t byte);
int RB_write(ringbuffer *b, const uint8_t *str, int l);
int RB_datalen(ringbuffer *b);
int RB_datalento(ringbuffer *b, uint8_t byte);
int RB_clearbuf(ringbuffer *b);

View File

@@ -21,9 +21,10 @@
#include "spi.h"
#include "hardware.h"
#ifdef EBUG
#include "usb.h"
#include "usb_dev.h"
#endif
#include "proto.h"
#include "strfunc.h"
/*
static void mymemcpy(uint8_t *dest, uint8_t *src, int len){
@@ -56,26 +57,26 @@ uint8_t SPI_transmit(uint8_t *buf, uint8_t len){
if(!buf || !len) return 0; // bad data format
if(SPI_status != SPI_READY) return 0; // spi not ready to transmit data
#ifdef EBUG
USB_send("SPI send "); USB_send(u2str(len));
USB_send("bytes, data: ");
USB_sendstr("SPI send "); USB_sendstr(u2str(len));
USB_sendstr("bytes, data: ");
#endif
for(uint8_t x = 0; x < len; ++x){
WAITX(!(SPI1->SR & SPI_SR_TXE));
SPI1->DR = buf[x];
WAITX(!(SPI1->SR & SPI_SR_BSY));
#ifdef EBUG
USB_send(u2hexstr(buf[x])); USB_send(", ");
USB_sendstr(uhex2str(buf[x])); USB_sendstr(", ");
#endif
WAITX(!(SPI1->SR & SPI_SR_RXNE));
buf[x] = SPI1->DR;
for(int ctr = 0; ctr < 3600; ++ctr) IWDG->KR = IWDG_REFRESH; // ~100mks delay
}
#ifdef EBUG
USB_send("\nReceive: ");
USB_sendstr("\nReceive: ");
for(int i = 0; i < len; ++i){
USB_send(u2hexstr(buf[i])); USB_send(", ");
USB_sendstr(uhex2str(buf[i])); USB_sendstr(", ");
}
USB_send("\n");
USB_sendstr("\n");
#endif
return len;
}

View File

@@ -0,0 +1,265 @@
/*
* This file is part of the test 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 "strfunc.h"
/**
* @brief hexdump - dump hex array by 16 bytes in string
* @param sendfun - function to send data
* @param arr - array to dump
* @param len - length of `arr`
*/
void hexdump(int (*sendfun)(const char *s), uint8_t *arr, uint16_t len){
char buf[52], *bptr = buf;
for(uint16_t l = 0; l < len; ++l, ++arr){
for(int16_t j = 1; j > -1; --j){
register uint8_t half = (*arr >> (4*j)) & 0x0f;
if(half < 10) *bptr++ = half + '0';
else *bptr++ = half - 10 + 'a';
}
if(l % 16 == 15){
*bptr++ = '\n';
*bptr = 0;
sendfun(buf);
bptr = buf;
}else *bptr++ = ' ';
}
if(bptr != buf){
*bptr++ = '\n';
*bptr = 0;
sendfun(buf);
}
}
/**
* @brief _2str - convert value into string buffer
* @param val - |value|
* @param minus - ==0 if value > 0
* @return buffer with number
*/
static const char *_2str(uint32_t val, uint8_t minus){
static char strbuf[12];
char *bufptr = &strbuf[11];
*bufptr = 0;
if(!val){
*(--bufptr) = '0';
}else{
while(val){
uint32_t x = val / 10;
*(--bufptr) = (val - 10*x) + '0';
val = x;
}
}
if(minus) *(--bufptr) = '-';
return bufptr;
}
// return string with number `val`
const char *u2str(uint32_t val){
return _2str(val, 0);
}
const char *i2str(int32_t i){
uint8_t minus = 0;
uint32_t val;
if(i < 0){
minus = 1;
val = -i;
}else val = i;
return _2str(val, minus);
}
/**
* @brief uhex2str - print 32bit unsigned int as hex
* @param val - value
* @return string with number
*/
const char *uhex2str(uint32_t val){
static char buf[12] = "0x";
int npos = 2;
uint8_t *ptr = (uint8_t*)&val + 3;
int8_t i, j, z=1;
for(i = 0; i < 4; ++i, --ptr){
if(*ptr == 0){ // omit leading zeros
if(i == 3) z = 0;
if(z) continue;
}
else z = 0;
for(j = 1; j > -1; --j){
uint8_t half = (*ptr >> (4*j)) & 0x0f;
if(half < 10) buf[npos++] = half + '0';
else buf[npos++] = half - 10 + 'a';
}
}
buf[npos] = 0;
return buf;
}
/**
* @brief omit_spaces - eliminate leading spaces and other trash in string
* @param buf - string
* @return - pointer to first character in `buf` > ' '
*/
const char *omit_spaces(const char *buf){
while(*buf){
if(*buf > ' ') break;
++buf;
}
return buf;
}
/**
* @brief getdec - read decimal number & return pointer to next non-number symbol
* @param buf - string
* @param N - number read
* @return Next non-number symbol. In case of overflow return `buf` and N==0xffffffff
*/
static const char *getdec(const char *buf, uint32_t *N){
char *start = (char*)buf;
uint32_t num = 0;
while(*buf){
char c = *buf;
if(c < '0' || c > '9'){
break;
}
if(num > 429496729 || (num == 429496729 && c > '5')){ // overflow
*N = 0xffffff;
return start;
}
num *= 10;
num += c - '0';
++buf;
}
*N = num;
return buf;
}
// read hexadecimal number (without 0x prefix!)
static const char *gethex(const char *buf, uint32_t *N){
const char *start = buf;
uint32_t num = 0;
while(*buf){
char c = *buf;
uint8_t M = 0;
if(c >= '0' && c <= '9'){
M = '0';
}else if(c >= 'A' && c <= 'F'){
M = 'A' - 10;
}else if(c >= 'a' && c <= 'f'){
M = 'a' - 10;
}
if(M){
if(num & 0xf0000000){ // overflow
*N = 0xffffff;
return start;
}
num <<= 4;
num += c - M;
}else{
break;
}
++buf;
}
*N = num;
return buf;
}
// read octal number (without 0 prefix!)
static const char *getoct(const char *buf, uint32_t *N){
const char *start = (char*)buf;
uint32_t num = 0;
while(*buf){
char c = *buf;
if(c < '0' || c > '7'){
break;
}
if(num & 0xe0000000){ // overflow
*N = 0xffffff;
return start;
}
num <<= 3;
num += c - '0';
++buf;
}
*N = num;
return buf;
}
// read binary number (without b prefix!)
static const char *getbin(const char *buf, uint32_t *N){
const char *start = (char*)buf;
uint32_t num = 0;
while(*buf){
char c = *buf;
if(c < '0' || c > '1'){
break;
}
if(num & 0x80000000){ // overflow
*N = 0xffffff;
return start;
}
num <<= 1;
if(c == '1') num |= 1;
++buf;
}
*N = num;
return buf;
}
/**
* @brief getnum - read uint32_t from string (dec, hex or bin: 127, 0x7f, 0b1111111)
* @param buf - buffer with number and so on
* @param N - the number read
* @return pointer to first non-number symbol in buf
* (if it is == buf, there's no number or if *N==0xffffffff there was overflow)
*/
const char *getnum(const char *txt, uint32_t *N){
const char *nxt = NULL;
const char *s = omit_spaces(txt);
if(*s == '0'){ // hex, oct or 0
if(s[1] == 'x' || s[1] == 'X'){ // hex
nxt = gethex(s+2, N);
if(nxt == s+2) nxt = (char*)txt;
}else if(s[1] > '0'-1 && s[1] < '8'){ // oct
nxt = getoct(s+1, N);
if(nxt == s+1) nxt = (char*)txt;
}else{ // 0
nxt = s+1;
*N = 0;
}
}else if(*s == 'b' || *s == 'B'){
nxt = getbin(s+1, N);
if(nxt == s+1) nxt = (char*)txt;
}else{
nxt = getdec(s, N);
if(nxt == s) nxt = (char*)txt;
}
return nxt;
}
// get signed integer
const char *getint(const char *txt, int32_t *I){
const char *s = omit_spaces(txt);
int32_t sign = 1;
uint32_t U;
if(*s == '-'){
sign = -1;
++s;
}
const char *nxt = getnum(s, &U);
if(nxt == s) return txt;
if(U & 0x80000000) return txt; // overfull
*I = sign * (int32_t)U;
return nxt;
}

View File

@@ -0,0 +1,30 @@
/*
* This file is part of the test 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>
#include <string.h>
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 *getnum(const char *txt, uint32_t *N);
const char *omit_spaces(const char *buf);
const char *getint(const char *txt, int32_t *I);

View File

@@ -26,7 +26,7 @@ static const uint8_t USB_DeviceDescriptor[] = {
USB_DT_DEVICE, // bDescriptorType
L16(bcdUSB), // bcdUSB_L
H16(bcdUSB), // bcdUSB_H
USB_CLASS_COMM, // bDeviceClass
USB_CLASS_MISC, // bDeviceClass
bDeviceSubClass, // bDeviceSubClass
bDeviceProtocol, // bDeviceProtocol
USB_EP0BUFSZ, // bMaxPacketSize
@@ -140,7 +140,7 @@ static const uint8_t USB_ConfigDescriptor[] = {
//const uint8_t HID_ReportDescriptor[];
_USB_LANG_ID_(LD, LANG_US);
_USB_STRING_(SD, u"0.1.0");
_USB_STRING_(SD, u"0.1.1");
_USB_STRING_(MD, u"eddy@sao.ru");
_USB_STRING_(PD, u"USB/CAN Canon lens controller");
_USB_STRING_(ID, u"canonlens");

View File

@@ -15,7 +15,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stm32f1.h>
#include <string.h>
#include "ringbuffer.h"
@@ -41,10 +40,10 @@
static volatile uint8_t bufovrfl = 0;
// receive buffer: hold data until chkin() call
static uint8_t volatile rcvbuf[USB_RXBUFSZ];
static uint8_t volatile rcvbuf[USB_RXBUFSZ] __attribute__((aligned(4)));
static uint8_t volatile rcvbuflen = 0;
// line coding
usb_LineCoding WEAK lineCoding = {115200, 0, 0, 8};
usb_LineCoding lineCoding = {115200, 0, 0, 8};
// CDC configured and ready to use
volatile uint8_t CDCready = 0;
@@ -70,10 +69,10 @@ static void chkin(){
// called from transmit EP to send next data portion or by user - when new transmission starts
static void send_next(){
uint8_t usbbuff[USB_TXBUFSZ];
uint8_t usbbuff[USB_TXBUFSZ] __attribute__((aligned(4)));
int buflen = RB_read((ringbuffer*)&rbout, (uint8_t*)usbbuff, USB_TXBUFSZ);
if(buflen == 0){
if(lastdsz == 64) EP_Write(1, NULL, 0); // send ZLP after 64 bits packet when nothing more to send
if(lastdsz == USB_TXBUFSZ) EP_Write(1, NULL, 0); // send ZLP after USB_TXBUFSZ bits packet when nothing more to send
lastdsz = 0;
return;
}else if(buflen < 0){
@@ -103,21 +102,20 @@ static void rxtx_handler(){
// weak handlers: change them somewhere else if you want to setup USART
// SET_LINE_CODING
void WEAK linecoding_handler(usb_LineCoding *lc){
void linecoding_handler(usb_LineCoding *lc){
lineCoding = *lc;
}
// SET_CONTROL_LINE_STATE
void WEAK clstate_handler(uint16_t val){
void clstate_handler(uint16_t val){
CDCready = val; // CONTROL_DTR | CONTROL_RTS -> interface connected; 0 -> disconnected
}
// SEND_BREAK
void WEAK break_handler(){
void break_handler(){
CDCready = 0;
}
// USB is configured: setup endpoints
void set_configuration(){
EP_Init(1, EP_TYPE_BULK, USB_TXBUFSZ, USB_RXBUFSZ, rxtx_handler); // IN1 and OUT1
@@ -166,7 +164,16 @@ int USB_sendall(){
int USB_send(const uint8_t *buf, int len){
if(!buf || !CDCready || !len) return FALSE;
while(len){
int a = RB_write((ringbuffer*)&rbout, buf, len);
IWDG->KR = IWDG_REFRESH;
int l = RB_datalen((ringbuffer*)&rbout);
if(l < 0) continue;
int portion = rbout.length - 1 - l;
if(portion < 1){
if(lastdsz == 0) send_next();
continue;
}
if(portion > len) portion = len;
int a = RB_write((ringbuffer*)&rbout, buf, portion);
if(a > 0){
len -= a;
buf += a;
@@ -217,7 +224,7 @@ int USB_receive(uint8_t *buf, int len){
* @brief USB_receivestr - get string up to '\n' and replace '\n' with 0
* @param buf - receiving buffer
* @param len - its length
* @return strlen or negative value indicating overflow (if so, string won't be ends with 0 and buffer should be cleared)
* @return strlen or negative value indicating overflow
*/
int USB_receivestr(char *buf, int len){
chkin();
@@ -226,14 +233,18 @@ int USB_receivestr(char *buf, int len){
bufovrfl = 0;
return -1;
}
int l = RB_readto((ringbuffer*)&rbin, '\n', (uint8_t*)buf, len);
if(l < 1){
int l = RB_datalento((ringbuffer*)&rbin, '\n');
if(l > len){ // can't read: line too long -> clear it
RB_readto((ringbuffer*)&rbin, '\n', NULL, 0);
return -1;
}else if(l < 1){ // nothing or no '\n' ?
if(rbin.length == RB_datalen((ringbuffer*)&rbin)){ // buffer is full but no '\n' found
while(1 != RB_clearbuf((ringbuffer*)&rbin));
return -1;
}
return 0;
}
l = RB_readto((ringbuffer*)&rbin, '\n', (uint8_t*)buf, len);
if(l == 0) return 0;
buf[l-1] = 0; // replace '\n' with strend
return l;

View File

@@ -16,7 +16,7 @@
*/
#pragma once
#include <stm32f1.h>
#include <stdint.h>
#include "usb_lib.h"
typedef struct {

View File

@@ -23,7 +23,7 @@
static ep_t endpoints[STM32ENDPOINTS];
static uint16_t USB_Addr = 0;
static uint8_t setupdatabuf[EP0DATABUF_SIZE];
static uint8_t setupdatabuf[EP0DATABUF_SIZE] __attribute__((aligned(4)));
static config_pack_t *setup_packet = (config_pack_t*) setupdatabuf;
volatile uint8_t usbON = 0; // device is configured and active
@@ -81,8 +81,7 @@ void WEAK usb_standard_request(){
break;
case REQ_RECIPIENT_ENDPOINT:
if(setup_packet->bRequest == CLEAR_FEATURE){
}else{
}
}else{ /* wrong */ }
break;
default:
break;
@@ -97,7 +96,7 @@ void WEAK usb_class_request(config_pack_t *req, uint8_t _U_ *data, uint16_t _U_
case SET_CONFIGURATION: // set featuring by req->wValue
break;
default:
break;
break;
}
if(0 == (setup_packet->bmRequestType & 0x80)) // host2dev
EP_WriteIRQ(0, NULL, 0);
@@ -119,7 +118,7 @@ bmRequestType: 76543210
*/
static void EP0_Handler(){
uint8_t ep0dbuflen = 0;
uint8_t ep0databuf[EP0DATABUF_SIZE];
uint8_t ep0databuf[EP0DATABUF_SIZE] __attribute__((aligned(4)));
uint16_t epstatus = KEEP_DTOG(USB->EPnR[0]); // EP0R on input -> return this value after modifications
int rxflag = RX_FLAG(epstatus);
// check direction
@@ -128,6 +127,7 @@ static void EP0_Handler(){
EP_Read(0, setupdatabuf);
// interrupt handler will be called later
}else if(epstatus & USB_EPnR_CTR_RX){ // data packet -> push received data to ep0databuf
//if(endpoints[0].rx_cnt){ }
ep0dbuflen = EP_Read(0, ep0databuf);
}
}
@@ -137,8 +137,7 @@ static void EP0_Handler(){
case REQ_TYPE_STANDARD:
if(SETUP_FLAG(epstatus)){
usb_standard_request();
}else{
}
}else{ }
break;
case REQ_TYPE_CLASS:
usb_class_request(setup_packet, ep0databuf, ep0dbuflen);
@@ -173,9 +172,14 @@ static void EP0_Handler(){
*/
void EP_WriteIRQ(uint8_t number, const uint8_t *buf, uint16_t size){
if(size > endpoints[number].txbufsz) size = endpoints[number].txbufsz;
#ifndef USB32
uint16_t N2 = (size + 1) >> 1;
// the buffer is 16-bit, so we should copy data as it would be uint16_t
uint16_t *buf16 = (uint16_t *)buf;
#else
int N4 = (size + 3) >> 2;
uint32_t *buf32 = (uint32_t *)buf;
#endif
#if defined USB1_16
// very bad: what if `size` is odd?
uint32_t *out = (uint32_t *)endpoints[number].tx_buf;
@@ -184,13 +188,19 @@ void EP_WriteIRQ(uint8_t number, const uint8_t *buf, uint16_t size){
}
#elif defined USB2_16
// use memcpy instead?
for(int i = 0; i < N2; i++){
for(int i = 0; i < N2; ++i){
endpoints[number].tx_buf[i] = buf16[i];
}
#elif defined USB32
for(int i = 0; i < N4; ++i) endpoints[number].tx_buf[i] = buf32[i];
#else
#error "Define USB1_16 or USB2_16"
#error "Define USB1_16 / USB2_16 / USB32"
#endif
#ifndef USB32
USB_BTABLE->EP[number].USB_COUNT_TX = size;
#else
USB_BTABLE->EP[number].USB_ADDR_COUNT_TX = (USB_BTABLE->EP[number].USB_ADDR_COUNT_TX & 0xffff) | (size << 16);
#endif
}
/**
@@ -225,8 +235,12 @@ int EP_Read(uint8_t number, uint8_t *buf){
// use memcpy instead?
for(int i = 0; i < sz; ++i)
buf[i] = endpoints[number].rx_buf[i];
#elif defined USB32
uint32_t *u32buf = (uint32_t*) buf;
int N4 = (sz + 3) >> 2;
for(int i = 0; i < N4; ++i) u32buf[i] = endpoints[number].rx_buf[i];
#else
#error "Define USB1_16 or USB2_16"
#error "Define USB1_16 / USB2_16 / USB32"
#endif
return sz;
}
@@ -243,11 +257,16 @@ static uint16_t lastaddr = LASTADDR_DEFAULT;
* @return 0 if all OK
*/
int EP_Init(uint8_t number, uint8_t type, uint16_t txsz, uint16_t rxsz, void (*func)(ep_t ep)){
#ifdef STM32G0
// in STM32G0 all buffers should be aligned by 32 bits
if(txsz & 3) txsz = ((txsz >> 2)+1) << 2;
if(rxsz & 3) rxsz = ((rxsz >> 2)+1) << 2;
#endif
if(number >= STM32ENDPOINTS) return 4; // out of configured amount
if(txsz > USB_BTABLE_SIZE/ACCESSZ || rxsz > USB_BTABLE_SIZE/ACCESSZ) return 1; // buffer too large
if(lastaddr + txsz + rxsz >= USB_BTABLE_SIZE/ACCESSZ) return 2; // out of btable
USB->EPnR[number] = (type << 9) | (number & USB_EPnR_EA);
USB->EPnR[number] ^= USB_EPnR_STAT_RX | USB_EPnR_STAT_TX_1;
USB->EPnR[number] ^= USB_EPnR_STAT_RX | USB_EPnR_STAT_TX;
if(rxsz & 1) return 3; // wrong rx buffer size
uint16_t countrx = 0;
if(rxsz < 64) countrx = rxsz / 2;
@@ -255,15 +274,28 @@ int EP_Init(uint8_t number, uint8_t type, uint16_t txsz, uint16_t rxsz, void (*f
if(rxsz & 0x1f) return 3; // should be multiple of 32
countrx = 31 + rxsz / 32;
}
USB_BTABLE->EP[number].USB_ADDR_TX = lastaddr;
#ifdef USB32
endpoints[number].tx_buf = (uint32_t *)(USB_BTABLE_BASE + lastaddr * ACCESSZ);
#else
endpoints[number].tx_buf = (uint16_t *)(USB_BTABLE_BASE + lastaddr * ACCESSZ);
#endif
endpoints[number].txbufsz = txsz;
lastaddr += txsz;
#ifdef USB32
USB_BTABLE->EP[number].USB_ADDR_COUNT_TX = (uint32_t) lastaddr;
#else
USB_BTABLE->EP[number].USB_ADDR_TX = lastaddr;
USB_BTABLE->EP[number].USB_COUNT_TX = 0;
USB_BTABLE->EP[number].USB_ADDR_RX = lastaddr;
#endif
lastaddr += txsz;
#ifdef USB32
endpoints[number].rx_buf = (uint32_t *)(USB_BTABLE_BASE + lastaddr * ACCESSZ);
USB_BTABLE->EP[number].USB_ADDR_COUNT_RX = (uint32_t) lastaddr | countrx << 26;
#else
endpoints[number].rx_buf = (uint8_t *)(USB_BTABLE_BASE + lastaddr * ACCESSZ);
lastaddr += rxsz;
USB_BTABLE->EP[number].USB_ADDR_RX = lastaddr;
USB_BTABLE->EP[number].USB_COUNT_RX = countrx << 10;
#endif
lastaddr += rxsz;
endpoints[number].func = func;
return 0;
}
@@ -272,7 +304,8 @@ int EP_Init(uint8_t number, uint8_t type, uint16_t txsz, uint16_t rxsz, void (*f
void USB_IRQ(){
uint32_t CNTR = USB->CNTR;
USB->CNTR = 0;
if(USB->ISTR & USB_ISTR_RESET){
uint32_t istr = USB->ISTR;
if(istr & USB_ISTR_RESET){
usbON = 0;
// Reinit registers
CNTR = USB_CNTR_RESETM | USB_CNTR_CTRM | USB_CNTR_SUSPM;
@@ -281,42 +314,56 @@ void USB_IRQ(){
lastaddr = LASTADDR_DEFAULT;
// clear address, leave only enable bit
USB->DADDR = USB_DADDR_EF;
USB->ISTR = ~USB_ISTR_RESET;
//USB->ISTR = ~(USB_ISTR_RESET); // clear all flags
if(EP_Init(0, EP_TYPE_CONTROL, USB_EP0BUFSZ, USB_EP0BUFSZ, EP0_Handler)){
return;
};
}
if(USB->ISTR & USB_ISTR_CTR){
if(istr & USB_ISTR_CTR){
// EP number
uint8_t n = USB->ISTR & USB_ISTR_EPID;
uint8_t n = istr & USB_ISTR_EPID;
if (istr & USB_ISTR_DIR){ // OUT
}else{ // IN
}
// copy received bytes amount
endpoints[n].rx_cnt = USB_BTABLE->EP[n].USB_COUNT_RX & 0x3FF; // low 10 bits is counter
endpoints[n].rx_cnt =
#ifdef USB32
(USB_BTABLE->EP[n].USB_ADDR_COUNT_RX >> 16) & 0x3FF;
#else
USB_BTABLE->EP[n].USB_COUNT_RX & 0x3FF; // low 10 bits is counter
#endif
// call EP handler
if(endpoints[n].func) endpoints[n].func();
}
if(USB->ISTR & USB_ISTR_WKUP){ // wakeup
#ifndef STM32F0
CNTR &= ~(USB_CNTR_FSUSP | USB_CNTR_LP_MODE | USB_CNTR_WKUPM); // clear suspend flags
#else
if(istr & USB_ISTR_WKUP){ // wakeup
#if defined STM32F0
CNTR &= ~(USB_CNTR_FSUSP | USB_CNTR_LPMODE | USB_CNTR_WKUPM);
#endif
USB->ISTR = ~USB_ISTR_WKUP;
}
if(USB->ISTR & USB_ISTR_SUSP){ // suspend -> still no connection, may sleep
usbON = 0;
#ifndef STM32F0
CNTR |= USB_CNTR_FSUSP | USB_CNTR_LP_MODE | USB_CNTR_WKUPM;
#elif defined STM32G0
CNTR &= ~(USB_CNTR_SUSPEN | USB_CNTR_PDWN | USB_CNTR_WKUPM);
#else
CNTR &= ~(USB_CNTR_FSUSP | USB_CNTR_LP_MODE | USB_CNTR_WKUPM); // clear suspend flags
#endif
//USB->ISTR = ~USB_ISTR_WKUP;
}
if(istr & USB_ISTR_SUSP){ // suspend -> still no connection, may sleep
usbON = 0;
#if defined STM32F0
CNTR |= USB_CNTR_FSUSP | USB_CNTR_LPMODE | USB_CNTR_WKUPM;
#elif defined STM32G0
CNTR |= USB_CNTR_SUSPEN | USB_CNTR_WKUPM;
#else
CNTR |= USB_CNTR_FSUSP | USB_CNTR_LP_MODE | USB_CNTR_WKUPM;
#endif
CNTR &= ~(USB_CNTR_SUSPM);
USB->ISTR = ~USB_ISTR_SUSP;
//USB->ISTR = ~USB_ISTR_SUSP;
}
USB->ISTR = 0; // clear all flags
USB->CNTR = CNTR; // rewoke interrupts
}
// here we suppose that all PIN settings done in hw_setup earlier
void USB_setup(){
lastaddr = LASTADDR_DEFAULT; // clear last address settings
#if defined STM32F3
NVIC_DisableIRQ(USB_LP_IRQn);
// remap USB LP & Wakeup interrupts to 75 and 76 - works only on pure F303
@@ -326,6 +373,7 @@ void USB_setup(){
NVIC_DisableIRQ(USB_LP_CAN1_RX0_IRQn);
NVIC_DisableIRQ(USB_HP_CAN1_TX_IRQn);
#elif defined STM32F0
// All is clocking from HSI48
NVIC_DisableIRQ(USB_IRQn);
RCC->APB1ENR |= RCC_APB1ENR_CRSEN;
RCC->CFGR3 &= ~RCC_CFGR3_USBSW; // reset USB
@@ -338,16 +386,33 @@ void USB_setup(){
CRS->CR |= CRS_CR_AUTOTRIMEN; // enable auto trim
CRS->CR |= CRS_CR_CEN; // enable freq counter & block CRS->CFGR as read-only
RCC->CFGR |= RCC_CFGR_SW;
#elif defined STM32G0
NVIC_DisableIRQ(USB_UCPD1_2_IRQn);
PWR->CR2 |= PWR_CR2_USV; // enable USB powering
//RCC->APBENR2 |= RCC_APBENR2_SYSCFGEN; // enable tacting of SYSCFG
// independent clocking of USB from HSI48
RCC->CR |= RCC_CR_HSI48ON;
uint32_t tmout = 16000000;
while(!(RCC->CR & RCC_CR_HSI48RDY)) if(--tmout == 0){ break;}
RCC->CCIPR2 &= ~RCC_CCIPR2_USBSEL; // select HSI48 for USB
RCC->APBENR1 |= RCC_APBENR1_CRSEN; // CRS clocking
CRS->CFGR = (31LL << CRS_CFGR_FELIM_Pos) | // tolerance (usually 31)
(48000LL / 1LL - 1LL) << CRS_CFGR_RELOAD_Pos | // 48MHz / 1kHZ (SOF)
CRS_CFGR_SYNCSRC_1; // USB SOF as sync source (0x2)
CRS->CR |= CRS_CR_AUTOTRIMEN | CRS_CR_CEN; // Enable autotrim and turn on Clock Recovery System
RCC->APBENR1 |= RCC_APBENR1_USBEN;
#endif
#ifndef STM32G0
RCC->APB1ENR |= RCC_APB1ENR_USBEN;
//??
USB->CNTR = USB_CNTR_FRES; // Force USB Reset
for(uint32_t ctr = 0; ctr < 72000; ++ctr) nop(); // wait >1ms
USB->CNTR = 0;
USB->BTABLE = 0;
#else
USB->CNTR = USB_CNTR_USBRST;
#endif
for(uint32_t ctr = 0; ctr < 72000; ++ctr) nop(); // wait >1ms
USB->CNTR = USB_CNTR_RESETM; // allow only reset interrupts
USB->DADDR = 0;
USB->ISTR = 0;
USB->CNTR = USB_CNTR_RESETM; // allow only reset interrupts
#if defined STM32F3
NVIC_EnableIRQ(USB_LP_IRQn);
#elif defined STM32F1
@@ -355,6 +420,9 @@ void USB_setup(){
#elif defined STM32F0
USB->BCDR |= USB_BCDR_DPPU;
NVIC_EnableIRQ(USB_IRQn);
#elif defined STM32G0
USB->BCDR |= USB_BCDR_DPPU; // turn ON DP pullup
NVIC_EnableIRQ(USB_UCPD1_2_IRQn);
#endif
}
@@ -365,4 +433,6 @@ void usb_lp_isr() __attribute__ ((alias ("USB_IRQ")));
void usb_lp_can_rx0_isr() __attribute__ ((alias ("USB_IRQ")));
#elif defined STM32F0
void usb_isr() __attribute__ ((alias ("USB_IRQ")));
#elif defined STM32G0
void usb_ucpd1_2_isr() __attribute__ ((alias ("USB_IRQ")));
#endif

View File

@@ -19,6 +19,10 @@
#include <stdint.h>
#include <wchar.h>
#ifndef _U_
#define _U_ __attribute__((unused))
#endif
/******************************************************************
* Hardware registers etc *
*****************************************************************/
@@ -30,6 +34,8 @@
#define USB_BASE ((uint32_t)0x40005C00)
#elif defined STM32F3
#include <stm32f3.h>
#elif defined STM32G0
#include <stm32g0.h>
#endif
// max endpoints number
@@ -38,14 +44,16 @@
* Buffers size definition
**/
// F0 - USB2_16; F1 - USB1_16; F3 - 1/2 depending on series
#if !defined USB1_16 && !defined USB2_16
// F0 - USB2_16; F1 - USB1_16; F3 - 1/2 depending on series; G0 - USB32
#if !defined USB1_16 && !defined USB2_16 && !defined USB32
#if defined STM32F0
#define USB2_16
#elif defined STM32F1
#define USB1_16
#elif defined STM32G0
#define USB32
#else
#error "Can't determine USB1_16 or USB2_16, define by hands"
#error "Can't determine USB1_16/USB2_16/USB32, define by hands"
#endif
#endif
@@ -59,8 +67,8 @@
#if defined STM32F0
#define USB_BTABLE_SIZE 1024
#elif defined STM32F3
#define USB_BTABLE_SIZE 512
#warning "Please, check real buffer size due to docs"
#define USB_BTABLE_SIZE 1024
//#warning "Please, check real buffer size due to docs"
#else
#error "define STM32F0 or STM32F3"
#endif
@@ -68,16 +76,21 @@
#if defined STM32F0
#define USB_BTABLE_SIZE 768
#elif defined STM32F3
#define USB_BTABLE_SIZE 512
#warning "Please, check real buffer size due to docs"
#define USB_BTABLE_SIZE 768
#elif defined STM32G0
#define USB_BTABLE_SIZE 2048
//#warning "Please, check real buffer size due to docs"
#else // STM32F103: 1024 bytes but with 32-bit addressing
#define USB_BTABLE_SIZE 1024
#endif
#endif // NOCAN
// first 64 bytes of USB_BTABLE are registers!
#ifndef STM32G0
#define USB_BTABLE_BASE 0x40006000
#else
#define USB_BTABLE_BASE 0x40009800
#endif
#define USB ((USB_TypeDef *) USB_BASE)
#ifdef USB_BTABLE
@@ -120,8 +133,12 @@ typedef struct {
__IO uint32_t ISTR;
__IO uint32_t FNR;
__IO uint32_t DADDR;
#ifndef USB32
__IO uint32_t BTABLE;
#ifdef STM32F0
#else
__IO uint32_t RESERVED1; // there's no BTABLE register in STM32G0
#endif
#if defined STM32F0 || defined USB32
__IO uint32_t LPMCSR;
__IO uint32_t BCDR;
#endif
@@ -135,16 +152,19 @@ typedef struct{
__IO uint16_t USB_ADDR_RX;
__IO uint16_t USB_COUNT_RX;
#define ACCESSZ (1)
#define BUFTYPE uint8_t
#elif defined USB1_16
__IO uint32_t USB_ADDR_TX;
__IO uint32_t USB_COUNT_TX;
__IO uint32_t USB_ADDR_RX;
__IO uint32_t USB_COUNT_RX;
#define ACCESSZ (2)
#define BUFTYPE uint16_t
#elif defined USB32
// 32-bit registers: addr & count in one!
__IO uint32_t USB_ADDR_COUNT_TX;
__IO uint32_t USB_ADDR_COUNT_RX;
#define ACCESSZ (1)
#else
#error "Define USB1_16 or USB2_16"
#error "Define USB1_16 (16 bits over 32bit register), USB2_16 (16 bits over 16 bit register) or USB32 (32 bist over 32 bit register)"
#endif
} USB_EPDATA_TypeDef;
@@ -303,9 +323,17 @@ typedef struct {
// endpoints state
typedef struct{
#ifdef USB32
uint32_t *tx_buf; // transmission buffer address
#else
uint16_t *tx_buf; // transmission buffer address
#endif
uint16_t txbufsz; // transmission buffer size
#ifdef USB32
uint32_t *rx_buf; // reception buffer address
#else
uint8_t *rx_buf; // reception buffer address
#endif
void (*func)(); // endpoint action function
unsigned rx_cnt : 10; // received data counter
} ep_t;

View File

@@ -1,2 +1,2 @@
#define BUILD_NUMBER "114"
#define BUILD_DATE "2025-03-12"
#define BUILD_NUMBER "133"
#define BUILD_DATE "2026-04-14"

View File

@@ -37,7 +37,7 @@ void adc_setup(){
ADC1->SMPR2 = ADC_SMPR2_SMP0;
ADC1->SMPR1 = ADC_SMPR1_SMP16 | ADC_SMPR1_SMP17;
// sequence order: 1[0]->3[1]->14[2]->15[3]->10[4]->11[5] -> 16[tsen] -> 17[vdd]
ADC1->SQR3 = (1 << 0) | (3<<5) | (14 << 10) | (15 << 15) | (10 << 20) | (11 < 25);
ADC1->SQR3 = (1 << 0) | (3<<5) | (14 << 10) | (15 << 15) | (10 << 20) | (11 << 25);
ADC1->SQR2 = (12 << 0) | (13 << 5) | (16 << 10) | (17 << 15);
ADC1->SQR1 = (ADC_CHANNELS - 1) << 20; // amount of conversions
ADC1->CR1 = ADC_CR1_SCAN; // scan mode

View File

@@ -8,3 +8,5 @@ LDLIBS := -lm
include ../makefile.f3
include ../../makefile.stm32
$(OBJDIR)/commproto.o: commproto.cpp $(VERSION_FILE)

View File

@@ -16,17 +16,26 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <math.h>
#ifdef EBUG
#include "strfunc.h"
#endif
#include "adc.h"
/**
* @brief ADCx_array - arrays for ADC channels with median filtering:
* ADC1:
* 0 - Ch0 - ADC1_IN1
* 1 - Ch1 - ADC1_IN2
* 2 - internal Tsens - ADC1_IN16
* 3 - Vref - ADC1_IN18
* 0 - Ch0 - ADC1_IN1 - NTC1
* 1 - Ch1 - ADC1_IN2 - NTC2
* 2 - Ch2 - ADC1_IN3 - NTC3
* 3 - Ch3 - ADC1_IN4 - NTC4
* 4 - internal Tsens - ADC1_IN16
* 5 - Vref - ADC1_IN18
* ADC2:
* 4 - AIN5/DAC_OUT1 - PA4 - DAC1_OUT1 (onboard heater?), PA5 - ADC2_IN2 (DAC output control)
* AIN5/DAC_OUT1 - PA4 - DAC1_OUT1 (onboard heater)
* 6 - PA5 - ADC2_IN2 (DAC output control)
*/
static uint16_t ADC_array[NUMBER_OF_ADC_CHANNELS*9];
@@ -65,17 +74,19 @@ TRUE_INLINE void enADC(ADC_TypeDef *chnl){
* ADC1 - DMA1_ch1
* ADC2 - DMA2_ch1
*/
// Setup ADC and DAC
// Setup ADC and DAC; ADC/DAC pins should be prepared in gpio_setup
void adc_setup(){
RCC->AHBENR |= RCC_AHBENR_ADC12EN; // Enable clocking
ADC12_COMMON->CCR = ADC_CCR_TSEN | ADC_CCR_VREFEN | ADC_CCR_CKMODE; // enable Tsens and Vref, HCLK/4
calADC(ADC1);
calADC(ADC2);
// ADC1: channels 1,2,16,18; ADC2: channel 2
ADC1->SMPR1 = ADC_SMPR1_SMP0 | ADC_SMPR1_SMP1;
// ADC1: channels 1,2,3,4,16,18
ADC1->SMPR1 = ADC_SMPR1_SMP0 | ADC_SMPR1_SMP1 | ADC_SMPR1_SMP2 | ADC_SMPR1_SMP3;
ADC1->SMPR2 = ADC_SMPR2_SMP15 | ADC_SMPR2_SMP17;
// 4 conversions in group: 1->2->16->18
ADC1->SQR1 = (1<<6) | (2<<12) | (16<<18) | (18<<24) | (NUMBER_OF_ADC1_CHANNELS-1);
// 6 conversions in group: 1->2->3->4->16->18
ADC1->SQR1 = (1<<6) | (2<<12) | (3<<18) | (4<<24) | (NUMBER_OF_ADC1_CHANNELS-1);
ADC1->SQR2 = (16<<0) | (18<<6);
// ADC2: channel 2
ADC2->SMPR1 = ADC_SMPR1_SMP1;
ADC2->SQR1 = (2<<6) | (NUMBER_OF_ADC2_CHANNELS-1);
// configure DMA for ADC
@@ -109,7 +120,8 @@ void adc_setup(){
* @param nch - number of channel
* @return
*/
uint16_t getADCval(int nch){
uint16_t getADCval(uint8_t nch){
if(nch >= NUMBER_OF_ADC_CHANNELS) return 0;
register uint16_t temp;
#define PIX_SORT(a,b) { if ((a)>(b)) PIX_SWAP((a),(b)); }
#define PIX_SWAP(a,b) { temp=(a);(a)=(b);(b)=temp; }
@@ -131,7 +143,7 @@ uint16_t getADCval(int nch){
}
// get voltage @input nch (V)
float getADCvoltage(int nch){
float getADCvoltage(uint8_t nch){
float v = getADCval(nch);
v *= getVdd();
v /= 4096.f; // 12bit ADC
@@ -155,3 +167,114 @@ float getVdd(){
vdd /= getADCval(ADC_VREF);
return vdd;
}
// R lookup table for T=-10..59 degreesC
#if 0
T=[-10:59]+273.15;
R=1000*exp(3950*(1./T-1/298.15));
for i=1:length(T); printf("\t%.1f,\t// %d \n", R(i), T(i)-273.15); endfor
#endif
static const float Rlut[] = {
5824.6, // -10
5502.8, // -9
5201.1, // -8
4917.9, // -7
4652.2, // -6
4402.6, // -5
4168.1, // -4
3947.7, // -3
3740.5, // -2
3545.5, // -1
3362.1, // 0
3189.3, // 1
3026.6, // 2
2873.3, // 3
2728.8, // 4
2592.5, // 5
2463.9, // 6
2342.5, // 7
2227.9, // 8
2119.7, // 9
2017.5, // 10
1920.8, // 11
1829.4, // 12
1743.0, // 13
1661.2, // 14
1583.7, // 15
1510.4, // 16
1440.9, // 17
1375.1, // 18
1312.7, // 19
1253.5, // 20
1197.4, // 21
1144.1, // 22
1093.6, // 23
1045.6, // 24
1000.0, // 25
956.7, // 26
915.5, // 27
876.4, // 28
839.1, // 29
803.7, // 30
770.0, // 31
737.9, // 32
707.4, // 33
678.3, // 34
650.6, // 35
624.1, // 36
598.9, // 37
574.9, // 38
552.0, // 39
530.1, // 40
509.3, // 41
489.4, // 42
470.3, // 43
452.2, // 44
434.8, // 45
418.2, // 46
402.4, // 47
387.2, // 48
372.7, // 49
358.8, // 50
345.5, // 51
332.8, // 52
320.7, // 53
309.0, // 54
297.8, // 55
287.1, // 56
276.9, // 57
267.1, // 58
257.7, // 59
};
#define LUTSZ (sizeof(Rlut) / sizeof(float))
/**
* @brief getNTCtemp - stupid LUT-search and linear approximation of T by R
* @param nch - channel of ADC for Tx
* @return temperature in degr.C
*/
float getNTCtemp(uint8_t nch){
if(nch > ADC_AIN4) return -300.f; // bad number
uint16_t val = getADCval(nch);
if(val < 5) return -400.f; // short cirquit
else if(val > 4090) return -500.f; // no NTC
float R = 1000.f / (4096.f / val - 1.f); // resistance of NTC
#ifdef EBUG
USB_sendstr("R="); USB_sendstr(float2str(R, 1)); newline();
#endif
int left = 0, right = LUTSZ-1;
if(R > Rlut[0]) right = 1;
else if(R < Rlut[LUTSZ-1]) left = LUTSZ-2;
while(right - left > 1){
int idx = left + (right - left) / 2;
float Rl = Rlut[idx];
if(Rl > R) left = idx + 1;
else right = idx - 1;
}
if(left >= (int)LUTSZ) return 60.f;
float Rleft = Rlut[left], Rright = Rlut[left+1];
float T = (float)left - 9.f - (R - Rright) / (Rleft - Rright);
return T;
}

View File

@@ -19,22 +19,27 @@
#pragma once
#include <stm32f3.h>
#define NUMBER_OF_ADC1_CHANNELS (4)
// 4 sensors on 1..4, TS (16) and Vdd (18)
#define NUMBER_OF_ADC1_CHANNELS (6)
#define NUMBER_OF_ADC2_CHANNELS (1)
// total number of channels - for array
#define NUMBER_OF_ADC_CHANNELS ((NUMBER_OF_ADC1_CHANNELS+NUMBER_OF_ADC2_CHANNELS))
// channels of ADC in array
#define ADC_AIN0 (0)
#define ADC_AIN1 (1)
#define ADC_TS (2)
#define ADC_VREF (3)
#define ADC_AIN5 (4)
#define ADC_AIN1 (0)
#define ADC_AIN2 (1)
#define ADC_AIN3 (2)
#define ADC_AIN4 (3)
#define ADC_NTCIN(x) ((x)-1)
#define ADC_TS (4)
#define ADC_VREF (5)
#define ADC_DACIN (6)
// starting index of ADC2
#define ADC2START (9*NUMBER_OF_ADC1_CHANNELS)
void adc_setup();
float getMCUtemp();
float getVdd();
uint16_t getADCval(int nch);
float getADCvoltage(int nch);
uint16_t getADCval(uint8_t nch);
float getADCvoltage(uint8_t nch);
float getNTCtemp(uint8_t nch);

Binary file not shown.

View File

@@ -0,0 +1,627 @@
#include <cstring>
extern "C"{
#include <stm32f3.h>
#include <math.h>
#include "adc.h"
#include "commproto.h"
#include "hardware.h"
#include "i2c.h"
#include "mlxproc.h"
#include "strfunc.h"
#include "usart.h"
#include "usb_dev.h"
}
// image aquisition time
const char* const Timage = "TIMAGE";
// Global senders to send info into other interface
static int (*usb_sender)(const char*) = nullptr;
static int (*usart_sender)(const char*) = nullptr;
static int (*usb_putb)(uint8_t) = nullptr;
static int (*usart_putb)(uint8_t) = nullptr;
static int (*usb_sendbin)(const uint8_t*, int) = nullptr;
static int (*usart_sendbin)(const uint8_t*, int) = nullptr;
// Function helpers
static int (*SEND)(const char*) = nullptr;
static int (*putb)(uint8_t) = nullptr;
static int (*sendbin)(const uint8_t*, int) = nullptr;
#define N() putb('\n')
#define printu(x) SEND(u2str(x))
#define printi(x) SEND(i2str(x))
#define printuhex(x) SEND(uhex2str(x))
#define printfl(x,n) SEND(float2str(x, n))
void set_senders(int (*usbs)(const char *),
int (*usbb)(uint8_t),
int (*usbbin)(const uint8_t *, int),
int (*usarts)(const char *),
int (*usartb)(uint8_t),
int (*usartbin)(const uint8_t *, int)){
usb_sender = usbs;
usb_putb = usbb;
usb_sendbin = usbbin;
usart_sender = usarts;
usart_putb = usartb;
usart_sendbin = usartbin;
}
// Local buffer for I2C data
#define LOCBUFFSZ 32
static uint16_t locBuffer[LOCBUFFSZ];
// default I2C address of sensor
static uint8_t I2Caddress = 0x33 << 1;
extern volatile uint32_t Tms;
// show `cartoon` - continuously draw ASCII image of current sensor
uint8_t cartoon = 0;
// Command list
#define COMMAND_TABLE \
COMMAND(help, "show this help") \
COMMAND(ascii, "draw nth image in ASCII (n=0..4)") \
COMMAND(binary, "get nth image as text array of floats") \
COMMAND(listids, "list active sensors IDs") \
COMMAND(tempmap, "show temperature map of nth image") \
COMMAND(acqtime, "show nth image aquisition time") \
COMMAND(bmereinit, "reinit BME280") \
COMMAND(environ, "get environment parameters") \
COMMAND(state, "get MLX state") \
COMMAND(reset, "reset MCU") \
COMMAND(time, "print current Tms") \
COMMAND(iicaddr, "get/set I2C address (non-shifted)") \
COMMAND(mlxcont, "continue MLX") \
COMMAND(iicspeed, "get/set I2C speed (0..4)") \
COMMAND(mlxpause, "pause MLX") \
COMMAND(mlxstop, "stop MLX") \
COMMAND(adc, "get n'th ADC values") \
COMMAND(ntc, "get n'th NTC temperatures") \
COMMAND(cartoon, "toggle cartoon mode") \
COMMAND(mlxdump, "dump MLX parameters for sensor n") \
COMMAND(mlxaddr, "get/set MLX address of sensor n") \
COMMAND(readreg, "read I2C register: readreg reg [= nwords]") \
COMMAND(writedata, "write I2C data: writedata = val1 val2 ...") \
COMMAND(iicscan, "scan I2C bus") \
COMMAND(mcutemp, "get MCU temperature") \
COMMAND(mcuvdd, "get MCU Vdd") \
COMMAND(dac, "get/set DAC value") \
COMMAND(pwm, "get/set PWM for channel n (0..100%)") \
COMMAND(sendstr, "send string to other interface: sendstr = text")
// Command prototypes
#define COMMAND(name, desc) static errcodes_t cmd_ ## name(const char*, char*);
COMMAND_TABLE
#undef COMMAND
// descrtiptions for `help`
typedef struct {
const char *name;
const char *desc;
} CmdInfo;
static const CmdInfo cmdInfo[] = {
#define COMMAND(name, desc) { #name, desc },
COMMAND_TABLE
#undef COMMAND
};
// Text descriptions for error codes
static const char* errtxt[ERR_AMOUNT] = {
[ERR_OK] = "OK\n",
[ERR_BADCMD] = "BADCMD\n",
[ERR_BADPAR] = "BADPAR\n",
[ERR_BADVAL] = "BADVAL\n",
[ERR_WRONGLEN] = "WRONGLEN\n",
[ERR_CANTRUN] = "CANTRUN\n",
[ERR_BUSY] = "BUSY\n",
[ERR_OVERFLOW] = "OVERFLOW\n",
};
const char *EQ = " = "; // equal sign for getters
// send `command = `
#define CMDEQ() do{SEND(cmd); SEND(EQ);}while(0)
// send `commandXXX = `
#define CMDEQP(x) do{SEND(cmd); SEND(u2str((uint32_t)x)); SEND(EQ);}while(0)
/**
* @brief splitargs - get command parameter and setter from `args`
* @param args (i) - rest of string after command (like `1 = PU OD OUT`)
* @param parno (o) - parameter number or -1 if none
* @return setter (part after `=` without leading spaces) or NULL if none
*/
static const char *splitargs(char *args, int32_t *parno){
if(!args) return NULL;
uint32_t U32;
const char *next = getnum(args, &U32);
int p = -1;
if(next != args && U32 <= MAXPARNO) p = U32;
if(parno) *parno = p;
next = strchr(next, '=');
if(next){
if(*(++next)) next = omit_spaces(next);
if(*next == 0) next = NULL;
}
return next;
}
/**
* @brief argsvals - split `args` into `parno` and setter's value
* @param args - rest of string after command
* @param parno (o) - parameter number or -1 if none
* @param parval - integer setter's value
* @return false if no setter or it's not a number, true - got setter's num
*/
static bool argsvals(char *args, int32_t *parno, int32_t *parval){
const char *setter = splitargs(args, parno);
if(!setter) return false;
int32_t I32;
const char *next = getint(setter, &I32);
if(next != setter && parval){
*parval = I32;
return true;
}
return false;
}
/************* List of proto functions for each command *************/
static errcodes_t cmd_help(const char*, char*){
SEND(REPOURL);
for(size_t i = 0; i < sizeof(cmdInfo)/sizeof(cmdInfo[0]); ++i){
SEND(cmdInfo[i].name);
SEND(" - ");
SEND(cmdInfo[i].desc);
SEND("\n");
}
return ERR_AMOUNT;
}
static errcodes_t cmd_time(const char* cmd, char*){
CMDEQ();
printu(Tms); N();
return ERR_AMOUNT;
}
static errcodes_t cmd_reset(const char*, char*){
NVIC_SystemReset();
return ERR_CANTRUN; // unreacheable
}
// send acquisition time
static void imaqtime(uint8_t sensno){
uint32_t T = mlx_lastimT(sensno);
SEND(Timage); printu(sensno); SEND(EQ);
printu(T); N();
}
// Common image command for ASCII/binary/tempmap
static errcodes_t image_cmd(char* args, int mode){
int32_t sensno = -1;
splitargs(args, &sensno);
if(sensno < 0 || sensno >= N_SENSORS) return ERR_BADPAR;
fp_t *img = mlx_getimage(sensno);
if(!img) return ERR_CANTRUN;
// Frame number
imaqtime(sensno);
switch(mode){
case 0: // tempmap
dumpIma(img);
break;
case 1: // ascii
drawIma(img);
break;
case 2: // binary
SEND("BINARY"); putb('0'+sensno); putb('=');
uint8_t *d = (uint8_t*)img;
uint32_t _2send = MLX_PIXNO * sizeof(float);
// send by portions of 256 bytes
while(_2send){
uint32_t portion = (_2send > 256) ? 256 : _2send;
if(sendbin(d, portion)){
_2send -= portion;
d += portion;
}
}
SEND("ENDIMAGE"); N();
break;
}
return ERR_AMOUNT;
}
static errcodes_t cmd_ascii(const char* , char* args){
return image_cmd(args, 1);
}
static errcodes_t cmd_binary(const char* , char* args){
return image_cmd(args, 2);
}
static errcodes_t cmd_tempmap(const char* , char* args){
return image_cmd(args, 0);
}
static errcodes_t cmd_acqtime(const char* , char* args){
int32_t sensno = -1;
splitargs(args, &sensno);
if(sensno < 0 || sensno >= N_SENSORS) return ERR_BADPAR;
imaqtime(sensno);
return ERR_AMOUNT;
}
static errcodes_t cmd_listids(const char*, char*){
int N = mlx_nactive();
if(!N) return ERR_CANTRUN;
uint8_t *ids = mlx_activeids();
SEND("Found "); printu(N); SEND(" active sensors:\n");
for(int i = 0; i < N_SENSORS; ++i){
if(ids[i]){
SEND("SENSID"); printu(i); SEND(EQ); printuhex(ids[i]>>1); N();
}
}
return ERR_AMOUNT;
}
static errcodes_t cmd_bmereinit(const char*, char*){
if(bme_init()) return ERR_OK;
return ERR_CANTRUN;
}
static errcodes_t cmd_environ(const char*, char*){
bme280_t env;
if(!get_environment(&env)) return ERR_CANTRUN;
SEND("TEMPERATURE="); printfl(env.T, 2); N();
SEND("SKYTEMPERATURE="); printfl(env.Tsky, 2); N();
SEND("PRESSURE_HPA="); printfl(env.P/100.f, 2); N();
SEND("PRESSURE_MM="); printfl(env.P * 0.00750062f, 2); N();
SEND("HUMIDITY="); printfl(env.H, 2); N();
SEND("TEMP_DEW="); printfl(env.Tdew, 1); N();
SEND("T_MEASUREMENT="); printu(env.Tmeas); N();
return ERR_OK;
}
static errcodes_t cmd_state(const char* cmd, char*){
static const char *states[] = {
[MLX_NOTINIT] = "not init",
[MLX_WAITPARAMS] = "wait parameters DMA read",
[MLX_WAITSUBPAGE] = "wait subpage",
[MLX_READSUBPAGE] = "wait subpage DMA read",
[MLX_RELAX] = "do nothing"
};
mlx_state_t s = mlx_state();
CMDEQ(); SEND(states[s]); N();
return ERR_AMOUNT;
}
/********** I2C commands **********/
static errcodes_t cmd_iicaddr(const char* cmd, char* args){
int32_t addr;
if(argsvals(args, NULL, &addr)){
if(addr < 0 || addr > 0x7f) return ERR_BADVAL;
I2Caddress = (uint8_t)(addr << 1);
mlx_sethwaddr(I2Caddress, addr);
return ERR_AMOUNT;
}
// getter
CMDEQ(); printuhex(I2Caddress >> 1); N();
return ERR_AMOUNT;
}
static errcodes_t cmd_mlxcont(const char*, char*){
mlx_continue();
return ERR_OK;
}
static errcodes_t cmd_iicspeed(const char* cmd, char* args){
static const char *speeds[] = {"10K","100K","400K","1M","2M"};
int32_t speed;
// TODO: allow string parameter
if(argsvals(args, NULL, &speed)){
if (speed < 0 || speed >= I2C_SPEED_AMOUNT) return ERR_BADVAL;
i2c_setup((i2c_speed_t)speed);
}
// getter
CMDEQ(); SEND(speeds[i2c_curspeed]); N();
return ERR_AMOUNT;
}
static errcodes_t cmd_mlxpause(const char*, char*){
mlx_pause();
return ERR_OK;
}
static errcodes_t cmd_mlxstop(const char*, char*){
mlx_stop();
return ERR_OK;
}
static errcodes_t cmd_adc(const char* cmd, char* args){
if(!args){ // show all values
for(uint8_t i = 0; i < NUMBER_OF_ADC_CHANNELS; ++i){
CMDEQP(i); printu(getADCval(i)); N();
}
return ERR_AMOUNT;
}
int32_t addr;
splitargs(args, &addr);
if(addr < 0 || addr >= NUMBER_OF_ADC_CHANNELS) return ERR_BADPAR;
CMDEQP(addr); printu(getADCval(static_cast<uint8_t>(addr))); N();
return ERR_AMOUNT;
}
static errcodes_t cmd_ntc(const char* cmd, char* args){
if(!args){ // show all values
for(uint8_t i = 0; i <= ADC_AIN4; ++i){
CMDEQP(i); printfl(getNTCtemp(i), 1); N();
}
return ERR_AMOUNT;
}
int32_t addr;
splitargs(args, &addr);
if(addr < 0 || addr > ADC_AIN4) return ERR_BADPAR;
CMDEQP(addr); printfl(getNTCtemp(static_cast<uint8_t>(addr)), 1); N();
return ERR_AMOUNT;
}
static errcodes_t cmd_cartoon(const char*, char*){
// TODO: should be getter/setter!
cartoon = !cartoon;
return ERR_OK;
}
static errcodes_t cmd_mlxdump(const char*, char* args){
int32_t sensno = -1;
splitargs(args, &sensno);
if (sensno < 0 || sensno >= N_SENSORS) return ERR_BADPAR;
MLX90640_params *params = mlx_getparams(sensno);
if(!params) return ERR_CANTRUN;
SEND("SENSNO="); printi(sensno); N();
SEND("kVdd="); printi(params->kVdd); N();
SEND("vdd25="); printi(params->vdd25); N();
SEND("KvPTAT="); printfl(params->KvPTAT, 4); N();
SEND("KtPTAT="); printfl(params->KtPTAT, 4); N();
SEND("vPTAT25="); printi(params->vPTAT25); N();
SEND("alphaPTAT="); printfl(params->alphaPTAT, 2); N();
SEND("gainEE="); printi(params->gainEE); N();
SEND("Pixel offset parameters:\n");
dumpIma(params->offset);
SEND("K_talpha:\n");
dumpIma(params->kta);
SEND("Kv: ");
for(int i = 0; i < 4; ++i) { printfl(params->kv[i], 2); putb(' '); }
N();
SEND("cpOffset="); printi(params->cpOffset[0]); SEND(", "); printi(params->cpOffset[1]); N();
SEND("cpKta="); printfl(params->cpKta, 2); N();
SEND("cpKv="); printfl(params->cpKv, 2); N();
SEND("tgc="); printfl(params->tgc, 2); N();
SEND("cpALpha="); printfl(params->cpAlpha[0], 2); SEND(", "); printfl(params->cpAlpha[1], 2); N();
SEND("KsTa="); printfl(params->KsTa, 2); N();
SEND("Alpha:\n");
dumpIma(params->alpha);
SEND("CT3="); printfl(params->CT[1], 2); N();
SEND("CT4="); printfl(params->CT[2], 2); N();
for(int i = 0; i < 4; ++i){
SEND("KsTo"); putb('0'+i); putb('='); printfl(params->KsTo[i], 2); N();
SEND("alphacorr"); putb('0'+i); putb('='); printfl(params->alphacorr[i], 2); N();
}
return ERR_AMOUNT;
}
static errcodes_t cmd_mlxaddr(const char* cmd, char* args){
int32_t sensno = -1;
if(!args || !*args) { // without args: show global address
//CMDEQ(); printuhex(I2Caddress>>1); N();
//return ERR_AMOUNT;
return ERR_BADPAR;
}
const char *setter = splitargs(args, &sensno);
if(sensno < 0 || sensno >= N_SENSORS) return ERR_BADPAR;
if(setter){ // setter: set current address
uint32_t a;
const char *nxt = getnum(setter, &a);
if(nxt == setter || a > 0x7f) return ERR_BADVAL;
mlx_setaddr(sensno, (uint8_t)a);
return ERR_AMOUNT;
}else{ // getter
uint8_t a = mlx_getaddr(sensno);
CMDEQP(sensno); printuhex(a); N();
return ERR_AMOUNT;
}
}
static errcodes_t cmd_readreg(const char* cmd, char* args){
int32_t reg = -1, nwords = 1;
const char *setter = splitargs(args, &reg);
if(reg < 0) return ERR_BADPAR;
if(setter){ // read more than one byte
uint32_t n;
const char *nxt = getnum(setter, &n);
if (nxt == setter || n == 0 || n > I2C_BUFSIZE) return ERR_BADVAL;
nwords = (int32_t)n;
}
uint16_t *data = i2c_read_reg16(I2Caddress, (uint16_t)reg, nwords, 0);
if(!data) return ERR_CANTRUN;
if(nwords == 1){
CMDEQP(reg); printuhex(*data); N();
}else{
CMDEQP(reg); N(); hexdump16(SEND, data, nwords);
}
return ERR_AMOUNT;
}
static errcodes_t cmd_writedata(const char*, char* args){
const char *setter = splitargs(args, NULL);
if(!setter) return ERR_BADVAL;
int N = 0;
const char *p = setter;
while (*p){
if(N >= LOCBUFFSZ) return ERR_AMOUNT;
uint32_t val;
p = getnum(p, &val);
if(p == setter) break; // not a number
locBuffer[N++] = (uint16_t)val;
p = omit_spaces(p);
if (!*p) break;
}
if(N == 0) return ERR_BADVAL;
if(!i2c_write(I2Caddress, locBuffer, N)) return ERR_CANTRUN;
return ERR_OK;
}
static errcodes_t cmd_iicscan(const char*, char*) {
i2c_init_scan_mode();
return ERR_OK;
}
static errcodes_t cmd_mcutemp(const char* cmd, char*){
CMDEQ(); printfl(getMCUtemp(), 2); N();
return ERR_AMOUNT;
}
static errcodes_t cmd_mcuvdd(const char* cmd, char*){
CMDEQ(); printfl(getVdd(), 2); N();
return ERR_AMOUNT;
}
static errcodes_t cmd_dac(const char* cmd, char* args){
int32_t val;
if(argsvals(args, NULL, &val)){
if(val < 0 || val > 4095) return ERR_BADVAL;
DAC1->DHR12R1 = static_cast<uint32_t>(val);
}
// getter
CMDEQ(); printu(DAC1->DHR12R1); N();
return ERR_AMOUNT;
}
static void showpwm(const char* cmd, uint8_t nch){
uint16_t ccr;
switch(nch){
case 0: ccr = TIM3->CCR1; break;
case 1: ccr = TIM3->CCR2; break;
case 2: ccr = TIM3->CCR3; break;
case 3: ccr = TIM3->CCR4; break;
default: return;
}
CMDEQP(nch); printu(ccr); N();
}
// four PWM channels: 1,2 - heaters, 3,4 - info
static errcodes_t cmd_pwm(const char* cmd, char* args){
int32_t ch = -1, val;
const char *setter = splitargs(args, &ch);
if(ch < 0 || ch > PWM_CH_MAX){ // all channels
for(uint8_t i = 0; i <= PWM_CH_MAX; ++i)
showpwm(cmd, i);
return ERR_AMOUNT;
}
if(setter){
if(!getint(setter, &val) || val < 0 || val > 100) return ERR_BADVAL;
if(!setPWM(static_cast<uint8_t>(ch), static_cast<uint32_t>(val)))
return ERR_CANTRUN;
}
// getter
showpwm(cmd, (uint8_t)ch);
return ERR_AMOUNT;
}
static errcodes_t cmd_sendstr(const char*, char* args) {
int32_t dummy;
const char *text = splitargs(args, &dummy);
if(!text || !*text) return ERR_BADVAL;
// switch to other interface
int (*other_sender)(const char*) = (SEND == usb_sender) ? usart_sender : usb_sender;
if (!other_sender) return ERR_CANTRUN;
other_sender(text);
other_sender("\n");
return ERR_OK;
}
static constexpr uint32_t hash(const char* str, uint32_t h = 0) {
return *str ? hash(str + 1, h + ((h << 7) ^ *str)) : h;
}
const char *parse_cmd(int (*sendfun)(const char*), char *buf) {
if(!buf || !*buf || !sendfun) return NULL;
SEND = sendfun;
if(sendfun == usb_sender){
putb = usb_putb;
sendbin = usb_sendbin;
}else{
putb = usart_putb;
sendbin = usart_sendbin;
}
char command[CMD_MAXLEN+1];
int i = 0;
while(*buf > '@' && i < CMD_MAXLEN) command[i++] = *buf++;
command[i] = 0;
while(*buf && *buf <= ' ') ++buf;
char *args = buf;
#ifdef EBUG
USB_sendstr("__args='"); USB_sendstr(args); USB_sendstr("'\n");
#endif
if(!*args) args = NULL;
uint32_t h = hash(command);
errcodes_t ecode = ERR_AMOUNT;
switch (h){
#define COMMAND(name, desc) case hash(#name): ecode = cmd_##name(command, args); break;
COMMAND_TABLE
#undef COMMAND
default:
SEND("Unknown command, try 'help'\n");
}
if(ecode < ERR_AMOUNT) return errtxt[ecode];
return NULL;
}
void dumpIma(const fp_t im[MLX_PIXNO]){
for(int row = 0; row < MLX_H; ++row){
for(int col = 0; col < MLX_W; ++col){
printfl(*im++, 1);
putb(' ');
}
N();
}
}
#define GRAY_LEVELS 16
static const char *const CHARS_16 = " .':;+*oxX#&%B$@";
void drawIma(const fp_t im[MLX_PIXNO]){
fp_t min_val = im[0], max_val = im[0];
const fp_t *iptr = im;
for(int row = 0; row < MLX_H; ++row)
for(int col = 0; col < MLX_W; ++col){
fp_t cur = *iptr++;
if (cur < min_val) min_val = cur;
else if (cur > max_val) max_val = cur;
}
fp_t range = max_val - min_val;
SEND("RANGE="); printfl(range, 3); N();
SEND("MIN="); printfl(min_val, 3); N();
SEND("MAX="); printfl(max_val, 3); N();
if(fabsf(range) < 0.001) range = 1.0f;
iptr = im;
char string[MLX_W+2];
string[MLX_W] = '\n'; string[MLX_W+1] = 0; // end of line
for(int row = 0; row < MLX_H; ++row){
for(int col = 0; col < MLX_W; ++col){
fp_t normalized = ((*iptr++) - min_val) / range;
int idx = (int)(normalized * GRAY_LEVELS);
if(idx < 0) idx = 0;
else if(idx >= GRAY_LEVELS) idx = GRAY_LEVELS-1;
string[col] = CHARS_16[idx];
}
SEND(string);
}
N();
}

View File

@@ -0,0 +1,61 @@
/*
* This file is part of the as3935 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 "mlx90640.h"
#include "version.inc"
#ifdef EBUG
#define RLSDBG "debug"
#else
#define RLSDBG "release"
#endif
#define REPOURL "https://github.com/eddyem/stm32samples/tree/master/F3:F303/MLX90640-allsky " RLSDBG " build #" BUILD_NUMBER "@" BUILD_DATE "\n"
// error codes for answer message
typedef enum{
ERR_OK, // all OK
ERR_BADCMD, // wrong command
ERR_BADPAR, // wrong parameter
ERR_BADVAL, // wrong value (for setter)
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;
// maximal length of command (without trailing zero)
#define CMD_MAXLEN 15
// maximal available parameter number (for 16-bit registers is 0xffff
#define MAXPARNO 0xffff
extern const char *EQ;
const char *parse_cmd(int (*sendfun)(const char*), char *buf);
void set_senders(int (*usbs)(const char*), int (*usbb)(uint8_t), int (*usbbin)(const uint8_t*, int),
int (*usarts)(const char*), int (*usartb)(uint8_t), int (*usartbin)(const uint8_t*, int));
extern const char *const Timage;
extern uint8_t cartoon;
void dumpIma(const fp_t im[MLX_PIXNO]);
void drawIma(const fp_t im[MLX_PIXNO]);

View File

@@ -51,16 +51,19 @@ TRUE_INLINE void iwdg_setup(){
TRUE_INLINE void gpio_setup(){
RCC->AHBENR |= RCC_AHBENR_GPIOAEN | RCC_AHBENR_GPIOBEN; // for USART and LEDs
for(int i = 0; i < 10000; ++i) nop();
// USB - alternate function 14 @ pins PA11/PA12; SWD - AF0 @PA13/14; USB pullup - PA15
// PA6 - PWM for external heater (TIM3_CH1 or TIM16_CH1); PA7 - PWM propto (humidity - 50%)
// USB - alternate function 14 @ pins PA11/PA12; SWD - AF0 @PA13/14
// PA6,PA7 - PWM for external heaters (TIM3_CH1, TIM3_CH2)
// PA0..PA4 - NTC in, PA5 - DAC_OUT1 (board heater), PA6 - ADC in for DAC out
// USART pins will be setup in usart.c
GPIOA->AFR[0] = AFRf(2, 6) | AFRf(2, 7);
GPIOA->AFR[1] = AFRf(14, 11) | AFRf(14, 12);
GPIOA->MODER = MODER_AI(0) | MODER_AI(1) | MODER_AI(4) | MODER_AI(5) | MODER_AF(6) |
MODER_AF(7) | MODER_AF(11) | MODER_AF(12) | MODER_AF(13) | MODER_AF(14) | MODER_O(15);
// PB0 - PWM propto Text (<=20 - 0%, >=30 - 100%), PB1 - PWM propto (Text-Tsky) (<=-5 - 0%, >=+35 - 100%) PB2 - SPI_CS
// force USB DP to low level for a while
GPIOA->MODER = MODER_AI(0) | MODER_AI(1) | MODER_AI(2) | MODER_AI(3) | MODER_AI(4) | MODER_AI(5) | MODER_AF(6) |
MODER_AF(7) | MODER_AF(11) | MODER_O(12) | MODER_AF(13) | MODER_AF(14) | MODER_O(15);
// PB0 - PWM propto Text (<=20 - 0%, >=30 - 100%), PB1 - PWM propto (Text-Tsky) (<=-5 - 0%, >=+35 - 100%) PB9 - SPI_CS
// SPI and I2C will be setup in spi.c and i2c.c
GPIOB->AFR[0] = AFRf(2, 0) | AFRf(2, 1);
GPIOB->MODER = MODER_AF(0) | MODER_AF(1) | MODER_O(2);
pin_set(GPIOB, 1<<1);
GPIOB->MODER = MODER_AF(0) | MODER_AF(1) | MODER_O(9);
SPI_CS_1();
}
@@ -85,7 +88,7 @@ TRUE_INLINE void pwm_setup(){
// change PWM value in percents; return 0 if `val` is bad or `ch` not 0..3
int setPWM(uint8_t ch, uint8_t val){
if(ch > 3 || val > PWM_CCR_MAX) return 0;
if(ch >= PWM_CH_MAX || val > PWM_CCR_MAX) return 0;
volatile uint32_t *CCRs = &(TIM3->CCR1);
CCRs[ch] = val;
return 1;
@@ -139,7 +142,6 @@ void bme_process(){
// set PWM duty propto humidity
float h = (Humidity - 50.f) * 2.f;
if(h < 0.f) h = 0.f; else if(h > 100.f) h = 100.f;
setPWM(PWM_CH_HUMIDITY, (uint8_t)h);
environment.Tmeas = Tms;
// set PWM duty propto external T
float t = (Temperature + 20.f) * 2.f;

View File

@@ -25,9 +25,9 @@
#define USBPU_ON() pin_clear(USBPU_port, USBPU_pin)
#define USBPU_OFF() pin_set(USBPU_port, USBPU_pin)
// SPI_CS - PB2
#define SPI_CS_1() pin_set(GPIOB, 1<<2)
#define SPI_CS_0() pin_clear(GPIOB, 1<<2)
// SPI_CS - PB9
#define SPI_CS_1() pin_set(GPIOB, 1<<9)
#define SPI_CS_0() pin_clear(GPIOB, 1<<9)
// interval of environment measurements, ms
#define ENV_MEAS_PERIOD (10000)
@@ -36,14 +36,11 @@
// Max PWM CCR1 value (->1)
#define PWM_CCR_MAX (100)
// PWM channels (start from 0 - CH1)
// external heater
#define PWM_CH_HEATER (0)
// propto humidity (the higher - the brighter)
#define PWM_CH_HUMIDITY (1)
// propto external T (the higher - the brighter)
#define PWM_CH_TEXT (2)
// propto Tsky - Text (the higher - the brighter)
#define PWM_CH_TSKY (3)
#define PWM_CH_MAX (3)
typedef struct{
float T; // temperature, degC

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 17.0.1, 2025-10-06T23:24:05. -->
<!-- Written by QtCreator 19.0.1, 2026-05-06T23:28:28. -->
<qtcreator>
<data>
<variable>EnvironmentId</variable>
@@ -86,6 +86,7 @@
<valuelist type="QVariantList" key="ClangTools.SuppressedDiagnostics"/>
<value type="bool" key="ClangTools.UseGlobalSettings">true</value>
</valuemap>
<value type="int" key="RcSync">0</value>
</valuemap>
</data>
<data>
@@ -153,6 +154,7 @@
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
<valuelist type="QVariantList" key="Analyzer.Valgrind.SuppressionFiles"/>
<valuelist type="QVariantList" key="CustomOutputParsers"/>
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
@@ -162,6 +164,7 @@
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
</valuemap>
@@ -185,6 +188,7 @@
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
<valuelist type="QVariantList" key="Analyzer.Valgrind.SuppressionFiles"/>
<valuelist type="QVariantList" key="CustomOutputParsers"/>
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
@@ -194,6 +198,7 @@
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
</valuemap>
@@ -204,10 +209,6 @@
<variable>ProjectExplorer.Project.TargetCount</variable>
<value type="qlonglong">1</value>
</data>
<data>
<variable>ProjectExplorer.Project.Updater.FileVersion</variable>
<value type="int">22</value>
</data>
<data>
<variable>Version</variable>
<value type="int">22</value>

View File

@@ -2,6 +2,8 @@ BMP280.c
BMP280.h
adc.c
adc.h
commproto.cpp
commproto.h
hardware.c
hardware.h
i2c.c
@@ -15,8 +17,6 @@ mlx90640.h
mlx90640_regs.h
mlxproc.c
mlxproc.h
proto.c
proto.h
ringbuffer.c
ringbuffer.h
spi.c

Before

Width:  |  Height:  |  Size: 344 B

After

Width:  |  Height:  |  Size: 354 B

View File

@@ -20,7 +20,7 @@
#include "hardware.h"
#include "i2c.h"
#include "mlxproc.h"
#include "proto.h"
#include "commproto.h"
#include "strfunc.h"
#include "usart.h"
#include "usb_dev.h"
@@ -43,14 +43,18 @@ int main(void){
StartHSI();
SysTick_Config((uint32_t)48000); // 1ms
}
USBPU_OFF();
USBPU_OFF(); // for development board with managed pullup resistor
hw_setup();
adc_setup();
i2c_setup(I2C_SPEED_400K);
bme_init();
USB_setup();
usart_setup(115200);
// setup USB DP as alternate function - for sensors' board with constant pullup resistor
GPIOA->MODER = (GPIOA->MODER & ~GPIO_MODER_MODER12) | MODER_AF(12);
USBPU_ON();
USB_setup();
// set senders for abiliby of sending messages between interfaces
set_senders(USB_sendstr, USB_putbyte, USB_send, usart_sendstr, usart_putbyte, usart_send);
uint32_t ctr = Tms, Tlastima[N_SENSORS] = {0};
mlx_continue(); // init state machine
while(1){
@@ -63,7 +67,7 @@ int main(void){
int l = USB_receivestr(inbuff, MAXSTRLEN);
if(l < 0) USB_sendstr("USBOVERFLOW\n");
else if(l){
const char *ans = parse_cmd(inbuff, SEND_USB);
const char *ans = parse_cmd(USB_sendstr, inbuff);
if(ans) USB_sendstr(ans);
}
if(i2c_scanmode){ // send this to both
@@ -84,7 +88,6 @@ int main(void){
if(Tnow != Tlastima[i]){
fp_t *im = mlx_getimage(i);
if(im){
chsendfun(SEND_USB);
//U(Sensno); UN(i2str(i));
U(Timage); USB_putbyte('0'+i); USB_putbyte('='); UN(u2str(Tnow));
drawIma(im);
@@ -96,7 +99,7 @@ int main(void){
if(usart_ovr()) usart_sendstr("USART_OVERFLOW\n");
char *got = usart_getline(NULL);
if(got){
const char *ans = parse_cmd(got, SEND_USART);
const char *ans = parse_cmd(usart_sendstr, got);
if(ans) usart_sendstr(ans);
}
bme_process();

View File

@@ -63,12 +63,16 @@ static int sensno = -1;
mlx_state_t mlx_state(){ return MLX_state; }
// set address
int mlx_setaddr(int n, uint8_t addr){
if(n < 0 || n > N_SENSORS) return 0;
if(n < 0 || n >= N_SENSORS) return 0;
if(addr > 0x7f) return 0;
sens_addresses[n] = addr << 1;
Tlastimage[n] = Tms; // refresh counter for autoreset I2C in case of error
return 1;
}
uint8_t mlx_getaddr(int n){
if(n < 0 || n >= N_SENSORS) return 0;
return sens_addresses[n];
}
// pause state machine and stop
void mlx_pause(){
MLX_oldstate = MLX_state;

View File

@@ -39,6 +39,7 @@ typedef enum{
} mlx_state_t;
int mlx_setaddr(int n, uint8_t addr);
uint8_t mlx_getaddr(int n);
mlx_state_t mlx_state();
int mlx_nactive();
uint8_t *mlx_activeids();

View File

@@ -1,539 +0,0 @@
/*
* This file is part of the ir-allsky project.
* Copyright 2025 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 <math.h>
#include <stm32f3.h>
#include <string.h>
#include "adc.h"
#include "hardware.h"
#include "i2c.h"
#include "mlxproc.h"
#include "proto.h"
#include "strfunc.h"
#include "usart.h"
#include "usb_dev.h"
#include "version.inc"
#define LOCBUFFSZ (32)
// local buffer for I2C data to send
static uint16_t locBuffer[LOCBUFFSZ];
static uint8_t I2Caddress = 0x33 << 1;
extern volatile uint32_t Tms;
uint8_t cartoon = 0; // "cartoon" mode: refresh image each time we get new
// functions to send data over USB or USART: to change them use flag in `parse_cmd`
typedef struct{
int (*S)(const char*); // send string
int (*P)(uint8_t); // put byte
int (*B)(const uint8_t*, int); // send raw bytes
} sendfun_t;
static sendfun_t usbsend = {
.S = USB_sendstr, .P = USB_putbyte, .B = USB_send
};
static sendfun_t usartsend = {
.S = usart_sendstr, .P = usart_putbyte, .B = usart_send
};
static sendfun_t *sendfun = &usbsend;
void chsendfun(int sendto){
if(sendto == SEND_USB) sendfun = &usbsend;
else sendfun = &usartsend;
}
// newline
#define N() sendfun->P('\n')
#define printu(x) do{sendfun->S(u2str(x));}while(0)
#define printi(x) do{sendfun->S(i2str(x));}while(0)
#define printuhex(x) do{sendfun->S(uhex2str(x));}while(0)
#define printfl(x,n) do{sendfun->S(float2str(x, n));}while(0)
// common names for frequent keys
const char* const Timage = "TIMAGE";
const char* const Image = "IMAGE";
static const char *const Sensno = "SENSNO=";
static const char *const OK = "OK\n", *const ERR = "ERR\n";
const char *const helpstring =
"https://github.com/eddyem/stm32samples/tree/master/F3:F303/MLX90640multi build#" BUILD_NUMBER " @ " BUILD_DATE "\n"
" management of single IR bolometer MLX90640\n"
"dn - draw nth image in ASCII\n"
"gn - get nth image 'as is' - float array of 768x4 bytes\n"
"l - list active sensors IDs\n"
"mn - show temperature map of nth image\n"
"tn - show nth image aquisition time\n"
"B - reinit BME280\n"
"E - get environment parameters (temperature etc)\n"
"G - get MLX state\n"
"R - reset device\n"
"T - print current Tms\n"
" Debugging options:\n"
"aa - change I2C address to a (a should be non-shifted value!!!)\n"
"c - continue MLX\n"
"i0..4 - setup I2C with speed 10k, 100k, 400k, 1M or 2M (experimental!)\n"
"p - pause MLX\n"
"s - stop MLX (and start from zero @ 'c')\n"
"A - get ADC values\n"
"C - \"cartoon\" mode on/off (show each new image) - USB only!!!\n"
"Dn - dump MLX parameters for sensor number n\n"
"Ia addr [n] - set device address for interactive work or (with n) change address of n'th sensor\n"
"Ir reg n - read n words from 16-bit register\n"
"Iw words - send words (hex/dec/oct/bin) to I2C\n"
"Is - scan I2C bus\n"
"M - get MCU temperature and Vdd value\n"
"O - set output of DAC (0..4095)\n"
"Px - set PWM output (0..100%) or get current value\n"
"Us - send string 's' to other interface\n"
;
TRUE_INLINE const char *setupI2C(char *buf){
static const char * const speeds[I2C_SPEED_AMOUNT] = {
[I2C_SPEED_10K] = "10K",
[I2C_SPEED_100K] = "100K",
[I2C_SPEED_400K] = "400K",
[I2C_SPEED_1M] = "1M",
[I2C_SPEED_2M] = "2M"
};
if(buf && *buf){
buf = omit_spaces(buf);
int speed = *buf - '0';
if(speed < 0 || speed >= I2C_SPEED_AMOUNT){
return ERR;
}
i2c_setup((i2c_speed_t)speed);
}
sendfun->S("I2CSPEED="); sendfun->S(speeds[i2c_curspeed]); N();
return NULL;
}
TRUE_INLINE const char *chhwaddr(const char *buf){
uint32_t a;
if(buf && *buf){
const char *nxt = getnum(buf, &a);
if(nxt && nxt != buf){
if(!mlx_sethwaddr(I2Caddress, a)) return ERR;
}else{
sendfun->S("Wrong number"); N();
return ERR;
}
}else{
sendfun->S("Need address"); N();
return ERR;
}
return OK;
}
// read sensor's number from `buf`; return -1 if error
static int getsensnum(const char *buf){
if(!buf || !*buf) return -1;
uint32_t num;
const char *nxt = getnum(buf, &num);
if(!nxt || nxt == buf || num >= N_SENSORS) return -1;
return (int) num;
}
TRUE_INLINE const char *chaddr(const char *buf){
uint32_t addr;
const char *nxt = getnum(buf, &addr);
if(nxt && nxt != buf){
if(addr > 0x7f) return ERR;
I2Caddress = (uint8_t) addr << 1;
int n = getsensnum(nxt);
if(n > -1) mlx_setaddr(n, addr);
}else addr = I2Caddress >> 1;
sendfun->S("I2CADDR="); sendfun->S(uhex2str(addr)); N();
return NULL;
}
// read I2C register[s] - only blocking read! (DMA allowable just for config/image reading in main process)
static const char *rdI2C(const char *buf){
uint32_t N = 0;
const char *nxt = getnum(buf, &N);
if(!nxt || buf == nxt || N > 0xffff) return ERR;
buf = nxt;
uint16_t reg = N, *b16 = NULL;
nxt = getnum(buf, &N);
if(!nxt || buf == nxt || N == 0 || N > I2C_BUFSIZE) return ERR;
if(!(b16 = i2c_read_reg16(I2Caddress, reg, N, 0))) return ERR;
if(N == 1){
char b[5];
u16s(*b16, b);
b[4] = 0;
sendfun->S(b); N();
}else hexdump16(sendfun->S, b16, N);
return NULL;
}
// read N numbers from buf, @return 0 if wrong or none
TRUE_INLINE uint16_t readNnumbers(const char *buf){
uint32_t D;
const char *nxt;
uint16_t N = 0;
while((nxt = getnum(buf, &D)) && nxt != buf && N < LOCBUFFSZ){
buf = nxt;
locBuffer[N++] = (uint16_t) D;
}
return N;
}
static const char *wrI2C(const char *buf){
uint16_t N = readNnumbers(buf);
if(N == 0) return ERR;
for(int i = 0; i < N; ++i){
sendfun->S("byte "); sendfun->S(u2str(i));
sendfun->S(" :"); sendfun->S(uhex2str(locBuffer[i])); N();
}
if(!i2c_write(I2Caddress, locBuffer, N)) return ERR;
return OK;
}
static void dumpfarr(float *arr){
for(int row = 0; row < 24; ++row){
for(int col = 0; col < 32; ++col){
printfl(*arr++, 2); sendfun->P(' ');
}
N();
}
}
// dump MLX parameters
TRUE_INLINE void dumpparams(const char *buf){
int N = getsensnum(buf);
if(N < 0){ sendfun->S(ERR); return; }
MLX90640_params *params = mlx_getparams(N);
if(!params){ sendfun->S(ERR); return; }
N(); sendfun->S(Sensno); sendfun->S(i2str(N));
sendfun->S("\nkVdd="); printi(params->kVdd);
sendfun->S("\nvdd25="); printi(params->vdd25);
sendfun->S("\nKvPTAT="); printfl(params->KvPTAT, 4);
sendfun->S("\nKtPTAT="); printfl(params->KtPTAT, 4);
sendfun->S("\nvPTAT25="); printi(params->vPTAT25);
sendfun->S("\nalphaPTAT="); printfl(params->alphaPTAT, 2);
sendfun->S("\ngainEE="); printi(params->gainEE);
sendfun->S("\nPixel offset parameters:\n");
float *offset = params->offset;
for(int row = 0; row < 24; ++row){
for(int col = 0; col < 32; ++col){
printfl(*offset++, 2); sendfun->P(' ');
}
N();
}
sendfun->S("K_talpha:\n");
dumpfarr(params->kta);
sendfun->S("Kv: ");
for(int i = 0; i < 4; ++i){
printfl(params->kv[i], 2); sendfun->P(' ');
}
sendfun->S("\ncpOffset=");
printi(params->cpOffset[0]); sendfun->S(", "); printi(params->cpOffset[1]);
sendfun->S("\ncpKta="); printfl(params->cpKta, 2);
sendfun->S("\ncpKv="); printfl(params->cpKv, 2);
sendfun->S("\ntgc="); printfl(params->tgc, 2);
sendfun->S("\ncpALpha="); printfl(params->cpAlpha[0], 2);
sendfun->S(", "); printfl(params->cpAlpha[1], 2);
sendfun->S("\nKsTa="); printfl(params->KsTa, 2);
sendfun->S("\nAlpha:\n");
dumpfarr(params->alpha);
sendfun->S("\nCT3="); printfl(params->CT[1], 2);
sendfun->S("\nCT4="); printfl(params->CT[2], 2);
for(int i = 0; i < 4; ++i){
sendfun->S("\nKsTo"); sendfun->P('0'+i); sendfun->P('=');
printfl(params->KsTo[i], 2);
sendfun->S("\nalphacorr"); sendfun->P('0'+i); sendfun->P('=');
printfl(params->alphacorr[i], 2);
}
N();
}
// get MLX state
TRUE_INLINE void getst(){
static const char *states[] = {
[MLX_NOTINIT] = "not init",
[MLX_WAITPARAMS] = "wait parameters DMA read",
[MLX_WAITSUBPAGE] = "wait subpage",
[MLX_READSUBPAGE] = "wait subpage DMA read",
[MLX_RELAX] = "do nothing"
};
mlx_state_t s = mlx_state();
sendfun->S("MLXSTATE=");
sendfun->S(states[s]); N();
}
// `draw`==1 - draw, ==0 - show T map, 2 - send raw float array with prefix 'TIMAGEX=y\nIMAGEX=' and postfix "ENDIMAGE\n"
static const char *drawimg(const char *buf, int draw){
int sensno = getsensnum(buf);
if(sensno > -1){
uint32_t T = mlx_lastimT(sensno);
fp_t *img = mlx_getimage(sensno);
if(img){
//sendfun->S(Sensno); sendfun->S(u2str(sensno)); N();
sendfun->S(Timage); sendfun->P('0'+sensno); sendfun->P('='); sendfun->S(u2str(T)); N();
switch(draw){
case 0:
dumpIma(img);
break;
case 1:
drawIma(img);
break;
case 2:
sendfun->S(Image); sendfun->P('0'+sensno); sendfun->P('=');
uint8_t *d = (uint8_t*)img;
uint32_t _2send = MLX_PIXNO * sizeof(float);
// send by portions of 256 bytes (as image is larger than ringbuffer)
while(_2send){
uint32_t portion = (_2send > 256) ? 256 : _2send;
sendfun->B(d, portion);
_2send -= portion;
d += portion;
}
sendfun->S("ENDIMAGE"); N();
break;
}
return NULL;
}
}
return ERR;
}
TRUE_INLINE void listactive(){
int N = mlx_nactive();
if(!N){ sendfun->S("No active sensors found!\n"); return; }
uint8_t *ids = mlx_activeids();
sendfun->S("Found "); sendfun->P('0'+N);
sendfun->S(" active sensors:"); N();
for(int i = 0; i < N_SENSORS; ++i)
if(ids[i]){
sendfun->S("SENSID");
sendfun->S(u2str(i)); sendfun->P('=');
sendfun->S(uhex2str(ids[i] >> 1));
N();
}
}
static void getimt(const char *buf){
int sensno = getsensnum(buf);
if(sensno > -1){
sendfun->S(Timage); sendfun->P('0'+sensno); sendfun->P('='); sendfun->S(u2str(mlx_lastimT(sensno))); N();
}else sendfun->S(ERR);
}
TRUE_INLINE void getenv(){
bme280_t env;
if(!get_environment(&env)) sendfun->S("BADENVIRONMENT\n");
sendfun->S("TEMPERATURE="); sendfun->S(float2str(env.T, 2));
sendfun->S("\nSKYTEMPERATURE="); sendfun->S(float2str(env.Tsky, 2));
sendfun->S("\nPRESSURE_HPA="); sendfun->S(float2str(env.P/100.f, 2));
sendfun->S("\nPRESSURE_MM="); sendfun->S(float2str(env.P * 0.00750062f, 2));
sendfun->S("\nHUMIDITY="); sendfun->S(float2str(env.H, 2));
sendfun->S("\nTEMP_DEW="); sendfun->S(float2str(env.Tdew, 1));
sendfun->S("\nT_MEASUREMENT="); sendfun->S(u2str(env.Tmeas));
N();
}
TRUE_INLINE const char *DAC_chval(const char *buf){
uint32_t D;
const char *nxt = getnum(buf, &D);
if(!nxt || nxt == buf || D > 4095) return ERR;
DAC1->DHR12R1 = D;
return OK;
}
TRUE_INLINE void getADC(){
sendfun->S("AIN0="); sendfun->S(u2str(getADCval(ADC_AIN0)));
sendfun->S("\nAIN1="); sendfun->S(u2str(getADCval(ADC_AIN1)));
sendfun->S("\nAIN5="); sendfun->S(u2str(getADCval(ADC_AIN5)));
N();
}
TRUE_INLINE void getMCUvals(){
sendfun->S("MCUTEMP="); sendfun->S(float2str(getMCUtemp(), 2));
sendfun->S("\nMCUVDD="); sendfun->S(float2str(getVdd(), 2));
N();
}
TRUE_INLINE const char* setpwm(const char *buf){
uint32_t D;
if(!buf || !*buf){
sendfun->S("PWM1="); sendfun->S(u2str(TIM3->CCR1));
sendfun->S("\nPWM2="); sendfun->S(u2str(TIM3->CCR2));
sendfun->S("\nPWM3="); sendfun->S(u2str(TIM3->CCR3));
sendfun->S("\nPWM4="); sendfun->S(u2str(TIM3->CCR4));
N();
return NULL;
}
const char *nxt = getnum(buf, &D);
if(!nxt || nxt == buf || !setPWM(PWM_CH_HEATER, D)) return ERR;
return OK;
}
/**
* @brief parse_cmd - user string parser
* @param buf - user data
* @param isusb - ==1 to send answer over usb, else send over USART1
* @return answer OK/ERR or NULL
*/
const char *parse_cmd(char *buf, int sendto){
if(!buf || !*buf) return NULL;
chsendfun(sendto);
if(buf[1]){
switch(*buf++){ // "long" commands
case 'a':
return chhwaddr(buf);
case 'd':
return drawimg(buf, 1);
case 'g':
return drawimg(buf, 2);
case 'i':
return setupI2C(buf);
case 'm':
return drawimg(buf, 0);
case 't':
getimt(buf); return NULL;
case 'D':
dumpparams(buf);
return NULL;
break;
case 'I':
buf = omit_spaces(buf);
switch(*buf){
case 'a':
return chaddr(buf);
case 'r':
return rdI2C(buf);
case 'w':
return wrI2C(buf);
case 's':
i2c_init_scan_mode();
return OK;
default:
return ERR;
}
break;
case 'O':
return DAC_chval(buf);
case 'P':
return setpwm(buf);
case 'U':
if(sendto == SEND_USB) chsendfun(SEND_USART);
else chsendfun(SEND_USB);
if(sendfun->S(buf) && N()) return OK;
return ERR;
default:
return ERR;
}
}
switch(*buf){ // "short" (one letter) commands
case 'A':
getADC();
break;
case 'c':
mlx_continue(); return OK;
break;
case 'i': return setupI2C(NULL); // current settings
case 'l':
listactive();
break;
case 'p':
mlx_pause(); return OK;
break;
case 's':
mlx_stop(); return OK;
case 'B':
if(bme_init()) return OK;
return ERR;
case 'C':
if(sendto != SEND_USB) return ERR;
cartoon = !cartoon; return OK;
case 'E':
getenv();
break;
case 'G':
getst();
break;
case 'M':
getMCUvals();
break;
case 'P':
return setpwm(NULL);
case 'R':
NVIC_SystemReset();
break;
case 'T':
sendfun->S("T="); sendfun->S(u2str(Tms)); N();
break;
case '?': // help
case 'h':
case 'H':
sendfun->S(helpstring);
break;
default:
return ERR;
break;
}
return NULL;
}
// dump image as temperature matrix
void dumpIma(const fp_t im[MLX_PIXNO]){
for(int row = 0; row < MLX_H; ++row){
for(int col = 0; col < MLX_W; ++col){
printfl(*im++, 1);
sendfun->P(' ');
}
N();
}
}
#define GRAY_LEVELS (16)
// 16-level character set ordered by fill percentage (provided by user)
static const char *const CHARS_16 = " .':;+*oxX#&%B$@";
// draw image in ASCII-art
void drawIma(const fp_t im[MLX_PIXNO]){
// Find min and max values
fp_t min_val = im[0], max_val = im[0];
const fp_t *iptr = im;
for(int row = 0; row < MLX_H; ++row){
for(int col = 0; col < MLX_W; ++col){
fp_t cur = *iptr++;
if(cur < min_val) min_val = cur;
else if(cur > max_val) max_val = cur;
}
}
fp_t range = max_val - min_val;
sendfun->S("RANGE="); sendfun->S(float2str(range, 3));
sendfun->S("\nMIN="); sendfun->S(float2str(min_val, 3));
sendfun->S("\nMAX="); sendfun->S(float2str(max_val, 3)); N();
if(fabsf(range) < 0.001) range = 1.; // solid fill -> blank
// Generate and print ASCII art
iptr = im;
for(int row = 0; row < MLX_H; ++row){
for(int col = 0; col < MLX_W; ++col){
fp_t normalized = ((*iptr++) - min_val) / range;
// Map to character index (0 to 15)
int index = (int)(normalized * GRAY_LEVELS);
// Ensure we stay within bounds
if(index < 0) index = 0;
else if(index > (GRAY_LEVELS-1)) index = (GRAY_LEVELS-1);
sendfun->P(CHARS_16[index]);
}
N();
}
N();
}

View File

@@ -1,2 +1,2 @@
#define BUILD_NUMBER "44"
#define BUILD_DATE "2025-10-06"
#define BUILD_NUMBER "65"
#define BUILD_DATE "2026-05-06"