mirror of
https://github.com/eddyem/stm32samples.git
synced 2026-08-15 20:49:27 +03:00
Compare commits
9 Commits
013a0b041c
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
811d8bda5d | ||
|
|
40d0605218 | ||
|
|
970d73dc38 | ||
|
|
b8f3e5a695 | ||
|
|
940b60df99 | ||
|
|
2b4a9cbd84 | ||
|
|
f6178a8b84 | ||
|
|
9fb0956dcf | ||
|
|
8252d370a2 |
@@ -94,7 +94,7 @@ static int binarySearch(int r, const uint8_t *start, int stor_size){
|
||||
*/
|
||||
void flashstorage_init(){
|
||||
if(FLASH_SIZE > 0 && FLASH_SIZE < 20000){
|
||||
uint32_t flsz = FLASH_SIZE * blocksize; // size in bytes
|
||||
uint32_t flsz = FLASH_SIZE * 1024; // size in bytes
|
||||
flsz -= (uint32_t)(&__varsstart) - FLASH_BASE;
|
||||
maxCnum = flsz / sizeof(user_conf);
|
||||
}
|
||||
@@ -155,7 +155,7 @@ static int erase_flash(const void *start, const void *end){
|
||||
uint32_t nblocks = 1, flsz = 0;
|
||||
if(!end){ // erase all remaining
|
||||
if(FLASH_SIZE > 0 && FLASH_SIZE < 20000){
|
||||
flsz = FLASH_SIZE * blocksize; // size in bytes
|
||||
flsz = FLASH_SIZE * 1024; // size in bytes
|
||||
flsz -= (uint32_t)start - FLASH_BASE;
|
||||
}
|
||||
}else{ // erase a part
|
||||
|
||||
@@ -96,7 +96,7 @@ static int binarySearch(int r, const uint8_t *start, int stor_size){
|
||||
*/
|
||||
void flashstorage_init(){
|
||||
if(FLASH_SIZE > 0 && FLASH_SIZE < 20000){
|
||||
uint32_t flsz = FLASH_SIZE * blocksize; // size in bytes
|
||||
uint32_t flsz = FLASH_SIZE * 1024; // size in bytes
|
||||
flsz -= (uint32_t)(&__varsstart) - FLASH_BASE;
|
||||
maxCnum = flsz / sizeof(user_conf);
|
||||
//SEND("flsz="); printu(flsz);
|
||||
@@ -165,7 +165,7 @@ static int erase_flash(const void *start, const void *end){
|
||||
uint32_t nblocks = 1, flsz = 0;
|
||||
if(!end){ // erase all remaining
|
||||
if(FLASH_SIZE > 0 && FLASH_SIZE < 20000){
|
||||
flsz = FLASH_SIZE * blocksize; // size in bytes
|
||||
flsz = FLASH_SIZE * 1024; // size in bytes
|
||||
flsz -= (uint32_t)start - FLASH_BASE;
|
||||
}
|
||||
}else{ // erase a part
|
||||
|
||||
@@ -103,7 +103,7 @@ static int binarySearch(int r, const uint8_t *start, int stor_size){
|
||||
*/
|
||||
void flashstorage_init(){
|
||||
if(FLASH_SIZE > 0 && FLASH_SIZE < 20000){
|
||||
uint32_t flsz = FLASH_SIZE * blocksize; // size in bytes
|
||||
uint32_t flsz = FLASH_SIZE * 1024; // size in bytes
|
||||
flsz -= (uint32_t)(&__varsstart) - FLASH_BASE;
|
||||
maxCnum = flsz / sizeof(user_conf);
|
||||
}
|
||||
@@ -164,7 +164,7 @@ static int erase_flash(const void *start, const void *end){
|
||||
uint32_t nblocks = 1, flsz = 0;
|
||||
if(!end){ // erase all remaining
|
||||
if(FLASH_SIZE > 0 && FLASH_SIZE < 20000){
|
||||
flsz = FLASH_SIZE * blocksize; // size in bytes
|
||||
flsz = FLASH_SIZE * 1024; // size in bytes
|
||||
flsz -= (uint32_t)start - FLASH_BASE;
|
||||
}
|
||||
}else{ // erase a part
|
||||
|
||||
25
F3:F303/MLX90640-allsky/gnuplot/mkplot
Executable file
25
F3:F303/MLX90640-allsky/gnuplot/mkplot
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ $# != 2 ]]; then
|
||||
echo "Usage: $0 <input file> <output file>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
gnuplot << EOF
|
||||
set terminal jpeg size 800,600 enhanced font "Arial,10"
|
||||
set output '$2'
|
||||
|
||||
# Standard thermal palette
|
||||
set pm3d map # Enables 2D map mode
|
||||
set palette rgbformulae 33,13,10 # Classic blue-green-red temperature palette
|
||||
set view map # Forces 2D top-down view
|
||||
|
||||
set title "Temperature Distribution (degC)"
|
||||
set xlabel "X Coordinate"
|
||||
set ylabel "Y Coordinate"
|
||||
set cblabel "Temperature" # Colorbar label
|
||||
|
||||
# Plot matrix data as an image
|
||||
plot '$1' matrix with image
|
||||
EOF
|
||||
|
||||
@@ -81,7 +81,7 @@ static int binarySearch(int r, const uint8_t *start, int stor_size){
|
||||
*/
|
||||
void flashstorage_init(){
|
||||
if(FLASH_SIZE > 0 && FLASH_SIZE < 20000){
|
||||
uint32_t flsz = FLASH_SIZE * blocksize; // size in bytes
|
||||
uint32_t flsz = FLASH_SIZE * 1024; // size in bytes
|
||||
flsz -= (uint32_t)(&__varsstart) - FLASH_BASE;
|
||||
maxCnum = flsz / sizeof(user_conf);
|
||||
}
|
||||
@@ -149,7 +149,7 @@ static int erase_flash(const void *start, const void *end){
|
||||
if(!start) return 1;
|
||||
uint32_t startb = (((uint32_t)start - FLASH_BASE) + blocksize - 1) / blocksize, endb;
|
||||
if(!end){ // erase all remaining
|
||||
endb = FLASH_SIZE / blocksize;
|
||||
endb = FLASH_SIZE * 1024 / blocksize;
|
||||
}else{ // erase a part
|
||||
endb = (((uint32_t)end - FLASH_BASE) + blocksize - 1) / blocksize;
|
||||
}
|
||||
|
||||
13
G4:G431/CORDIC/Makefile
Normal file
13
G4:G431/CORDIC/Makefile
Normal file
@@ -0,0 +1,13 @@
|
||||
BINARY := cordic
|
||||
PREFIX := /usr/bin/arm-none-eabi
|
||||
CFLAGS = -std=c23
|
||||
CPPFLAGS = -std=c++11
|
||||
# MCU code
|
||||
MCU := G431xx
|
||||
# change this linking script depending on particular MCU model,
|
||||
LDSCRIPT := stm32g431xb.ld
|
||||
LDLIBS += -lm
|
||||
|
||||
include ../makefile.g4
|
||||
include ../../makefile.stm32
|
||||
|
||||
204
G4:G431/CORDIC/astro.c
Normal file
204
G4:G431/CORDIC/astro.c
Normal file
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* This file is part of the cordic 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 <math.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "astro.h"
|
||||
#include "cordic.h"
|
||||
|
||||
static int sincosflag = 0; // math.h
|
||||
// longitude/latitude + in rad/hrs
|
||||
//static float longitude = 41.44143375f, latitude = 43.6535278f;
|
||||
static float lat_rad = 43.6535278f * M_PIf / 180.f;
|
||||
static float long_hrs = 41.44143375f / 15.f;
|
||||
|
||||
static void sincosf_m(float angle, float *s, float *c){
|
||||
if(s) *s = sin(angle);
|
||||
if(c) *c = cos(angle);
|
||||
}
|
||||
|
||||
static void (*sincosf)(float, float*, float*) = sincosf_m;
|
||||
|
||||
void set_sincos(int iscordic){
|
||||
if(iscordic) sincosf = cordic_sincos;
|
||||
else sincosf = sincosf_m;
|
||||
sincosflag = iscordic;
|
||||
}
|
||||
|
||||
int get_sincos(){ return sincosflag; }
|
||||
|
||||
/* Helper functions for angle normalization (single precision) */
|
||||
static float normalize_degrees(float angle){
|
||||
angle = fmodf(angle, 360.0f);
|
||||
if (angle < 0.0f) angle += 360.0f;
|
||||
return angle;
|
||||
}
|
||||
|
||||
static float normalize_hours(float hours){
|
||||
hours = fmodf(hours, 24.0f);
|
||||
if (hours < 0.0f) hours += 24.0f;
|
||||
return hours;
|
||||
}
|
||||
|
||||
/* 1. Compute Modified Julian Date from UNIX time (seconds since 1970-01-01 00:00:00 UTC) */
|
||||
float MJD_from_unix(uint32_t t){
|
||||
return 40587.f + (float)t / 86400.0f;
|
||||
}
|
||||
|
||||
float LST_from_unix(uint32_t t){
|
||||
uint32_t days = t / 86400;
|
||||
uint32_t sec = t % 86400;
|
||||
|
||||
float mjd_int = 40587.0f + (float)days;
|
||||
|
||||
float T = (mjd_int - 51544.5f) / 36525.0f, T2 = T*T, T3 = T2*T;
|
||||
float gmst0_sec = 24110.54841f + 8640184.812866f*T + 0.093104f*T2 - 6.2e-6f*T3;
|
||||
float ut1_sec = (float)sec * 1.00273790935f;
|
||||
float gmst_sec = gmst0_sec + ut1_sec;
|
||||
float lst_hours = gmst_sec / 3600.0f + long_hrs;
|
||||
return normalize_hours(lst_hours);
|
||||
}
|
||||
|
||||
/* 3. Convert Hour Angle (HA) to Right Ascension (RA) and vice versa.
|
||||
All angles in degrees. LST is Local Sidereal Time in degrees. */
|
||||
float ha_to_ra(float ha, float lst){
|
||||
float ra = lst - ha;
|
||||
return normalize_degrees(ra);
|
||||
}
|
||||
|
||||
float ra_to_ha(float ra, float lst){
|
||||
float ha = lst - ra;
|
||||
// Hour angle is usually in range [-180,180)
|
||||
ha = normalize_degrees(ha);
|
||||
if (ha > 180.0f) ha -= 360.0f;
|
||||
return ha;
|
||||
}
|
||||
|
||||
/* 4. Convert Altitude-Azimuth coordinates to Equatorial (Hour Angle, Declination)
|
||||
and back. All angles in degrees. Azimuth is measured from North through East. */
|
||||
void altaz_to_hadec(float alt_deg, float az_deg, float *ha_deg, float *dec_deg){
|
||||
float alt = alt_deg * M_PIf / 180.0f;
|
||||
float az = az_deg * M_PIf / 180.0f;
|
||||
|
||||
float sin_alt, cos_alt, sin_az, cos_az, sin_lat, cos_lat;
|
||||
sincosf(alt, &sin_alt, &cos_alt);
|
||||
sincosf(az, &sin_az, &cos_az);
|
||||
sincosf(lat_rad, &sin_lat, &cos_lat);
|
||||
|
||||
/* Declination */
|
||||
float sin_dec = sin_alt * sin_lat + cos_alt * cos_lat * cos_az;
|
||||
float dec = asinf(sin_dec);
|
||||
|
||||
/* Hour angle (using atan2f for sign determination) */
|
||||
float x = sin_alt * cos_lat - cos_alt * sin_lat * cos_az;
|
||||
float y = -cos_alt * sin_az;
|
||||
float ha = atan2f(y, x); // radians
|
||||
|
||||
*ha_deg = ha * 180.0f / M_PIf;
|
||||
*dec_deg = dec * 180.0f / M_PIf;
|
||||
}
|
||||
|
||||
void hadec_to_altaz(float ha_deg, float dec_deg, float *alt_deg, float *az_deg){
|
||||
float ha = ha_deg * M_PIf / 180.0f;
|
||||
float dec = dec_deg * M_PIf / 180.0f;
|
||||
|
||||
float sin_dec, cos_dec, sin_ha, cos_ha, sin_lat, cos_lat;
|
||||
sincosf(dec, &sin_dec, &cos_dec);
|
||||
sincosf(ha, &sin_ha, &cos_ha);
|
||||
sincosf(lat_rad, &sin_lat, &cos_lat);
|
||||
|
||||
/* Altitude */
|
||||
float sin_alt = sin_lat * sin_dec + cos_lat * cos_dec * cos_ha;
|
||||
float alt = asinf(sin_alt);
|
||||
|
||||
/* Azimuth (from North through East) */
|
||||
float x = sin_dec * cos_lat - cos_dec * sin_lat * cos_ha;
|
||||
float y = -cos_dec * sin_ha;
|
||||
float az = atan2f(y, x); // radians, [-π, π]
|
||||
|
||||
*alt_deg = alt * 180.0f / M_PIf;
|
||||
*az_deg = az * 180.0f / M_PIf;
|
||||
if (*az_deg < 0.0f) *az_deg += 360.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculate refraction constants A and B for the model dZ = A*tan(Z) + B*tan^3(Z)
|
||||
*
|
||||
* This is a single-precision adaptation of the SOFA iauRefco / ERFA eraRefco function.
|
||||
* Optimized for microcontrollers without hardware double-precision support (e.g., STM32G431).
|
||||
*
|
||||
* @param phpa Pressure at the observer (hPa = mbar)
|
||||
* @param tc Ambient temperature at the observer (degrees C)
|
||||
* @param rh Relative humidity at the observer (range 0-1)
|
||||
* @param wl Wavelength (micrometers). Use 0.55 for optical, >100 for radio.
|
||||
* @param refa Output: tan(Z) coefficient (radians)
|
||||
* @param refb Output: tan^3(Z) coefficient (radians)
|
||||
*/
|
||||
static void refco_f32(float phpa, float tc, float rh, float wl, float *refa, float *refb) {
|
||||
// Restrict input parameters to safe values (clamp)
|
||||
float t = tc;
|
||||
if(t < -150.0f) t = -150.0f;
|
||||
if(t > 200.0f) t = 200.0f;
|
||||
|
||||
float p = phpa;
|
||||
if(p < 0.0f) p = 0.0f;
|
||||
if(p > 10000.0f) p = 10000.0f;
|
||||
|
||||
float r = rh;
|
||||
if(r < 0.0f) r = 0.0f;
|
||||
if(r > 1.0f) r = 1.0f;
|
||||
|
||||
float w = wl;
|
||||
if(w < 0.1f) w = 0.1f;
|
||||
if(w > 10.0f) w = 10.0f;
|
||||
|
||||
// Water vapour pressure at the observer
|
||||
float pw = 0.0f;
|
||||
if(p > 0.0f){
|
||||
// Saturation vapour pressure (empirical formula)
|
||||
float ps = powf(10.0f, (0.7859f + 0.03477f * t) / (1.0f + 0.00412f * t))
|
||||
* (1.0f + p * (4.5e-6f + 6e-10f * t * t));
|
||||
pw = r * ps / (1.0f - (1.0f - r) * ps / p);
|
||||
}
|
||||
|
||||
// Temperature in Kelvin
|
||||
float tk = t + 273.15f;
|
||||
|
||||
// Refractive index minus 1 at the observer (gamma = (n - 1) at the observer)
|
||||
float gamma;
|
||||
// Optical/IR: wavelength-dependent formula
|
||||
float wlsq = w * w;
|
||||
gamma = ((77.53484e-6f + (4.39108e-7f + 3.666e-9f / wlsq) / wlsq) * p
|
||||
- 11.2684e-6f * pw) / tk;
|
||||
|
||||
// Beta coefficient (from Stone, with empirical adjustments)
|
||||
float beta = 4.4474e-6f * tk;
|
||||
|
||||
// Refraction constants (from Green)
|
||||
if(refa) *refa = gamma * (1.0f - beta);
|
||||
if(refb) *refb = -gamma * (beta - gamma / 2.0f);
|
||||
}
|
||||
|
||||
//alt_corrected = alt_apparent + refraction
|
||||
float refraction(float phpa, float tc, float rh, float Z_rad){
|
||||
float A, B;
|
||||
refco_f32(phpa, tc, rh, 0.55, &A, &B);
|
||||
float tanZ = tanf(Z_rad);
|
||||
return A * tanZ + B * tanZ * tanZ * tanZ;
|
||||
}
|
||||
32
G4:G431/CORDIC/astro.h
Normal file
32
G4:G431/CORDIC/astro.h
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* This file is part of the cordic project.
|
||||
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
void set_sincos(int iscordic);
|
||||
int get_sincos();
|
||||
float MJD_from_unix(uint32_t t);
|
||||
//float LST_from_mjd(float mjd);
|
||||
float LST_from_unix(uint32_t t);
|
||||
float ha_to_ra(float ha, float lst);
|
||||
float ra_to_ha(float ra, float lst);
|
||||
void altaz_to_hadec(float alt_deg, float az_deg, float *ha_deg, float *dec_deg);
|
||||
void hadec_to_altaz(float ha_deg, float dec_deg, float *alt_deg, float *az_deg);
|
||||
float refraction(float phpa, float tc, float rh, float Z_rad);
|
||||
266
G4:G431/CORDIC/commproto.cpp
Normal file
266
G4:G431/CORDIC/commproto.cpp
Normal file
@@ -0,0 +1,266 @@
|
||||
/*
|
||||
* This file is part of the cordic 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 <math.h>
|
||||
#include <stm32g4.h>
|
||||
|
||||
#include "astro.h"
|
||||
#include "commproto.h"
|
||||
#include "cordic.h"
|
||||
#include "hardware.h"
|
||||
#include "strfunc.h"
|
||||
#include "test.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)")
|
||||
|
||||
#define STR_HELPER(x) #x
|
||||
#define STR(x) STR_HELPER(x)
|
||||
|
||||
// list of all commands and handlers
|
||||
#define COMMAND_TABLE \
|
||||
COMMAND(help, "show this help") \
|
||||
COMMAND(sets, "set sin-cos to cordic (1) or math (0)") \
|
||||
COMMAND(sincos, "calculate sin/cos for given angle in degrees") \
|
||||
COMMAND(testc, "test CORDIC function: sincos, sin, cos, atan, sqrt, log") \
|
||||
COMMAND(testm, "test math function: sin, cos, atan, sqrt, log") \
|
||||
COMMAND(time, "show MJD and LST for given UNIX-time") \
|
||||
|
||||
|
||||
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)
|
||||
// the same as last but with command as option and uint value
|
||||
#define SHOWPARU(cmd, x, val) do{SEND(cmd); SEND(u2str((uint32_t)x)); SEND(EQ); SEND(u2str(val));}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){
|
||||
if(parval) *parval = I32;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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 const char* parse_func_name(char *args){
|
||||
char *setter = splitargs(args, NULL);
|
||||
if(!setter) return nullptr;
|
||||
// remove trailing spaces
|
||||
char *p = setter;
|
||||
while(*p && *p > ' ') ++p;
|
||||
*p = 0;
|
||||
return setter;
|
||||
}
|
||||
|
||||
// calculate sin/cos
|
||||
static errcodes_t cmd_sincos(const char *, char *args){
|
||||
char *setter = splitargs(args, NULL);
|
||||
float f;
|
||||
if(!setter || !getfloat(setter, &f)) return ERR_BADVAL;
|
||||
f = f/180.f * M_PIf;
|
||||
SEND("mathsin="); SEND(float2str(sinf(f), 7));
|
||||
SEND("\nmathcos="); SEND(float2str(cosf(f), 7));
|
||||
float s, c;
|
||||
cordic_sincos(f, &s, &c);
|
||||
SEND("\ncordicsin="); SEND(float2str(s, 7));
|
||||
SEND("\ncordiccos="); SEND(float2str(c, 7));
|
||||
SEND("\n");
|
||||
return ERR_AMOUNT;
|
||||
}
|
||||
|
||||
// test math function
|
||||
static errcodes_t cmd_testm(const char*, char *args){
|
||||
const char *fname = parse_func_name(args);
|
||||
if(!fname) return ERR_BADPAR;
|
||||
uint32_t elapsed = 0;
|
||||
bool ok = true;
|
||||
if(strcmp(fname, "sin") == 0){
|
||||
elapsed = test_math_sin();
|
||||
}else if(strcmp(fname, "cos") == 0){
|
||||
elapsed = test_math_cos();
|
||||
}else if(strcmp(fname, "atan") == 0){
|
||||
elapsed = test_math_atan();
|
||||
}else if(strcmp(fname, "sqrt") == 0){
|
||||
elapsed = test_math_sqrt();
|
||||
}else if(strcmp(fname, "log") == 0){
|
||||
elapsed = test_math_log();
|
||||
}else{
|
||||
ok = false;
|
||||
}
|
||||
if(!ok) return ERR_BADVAL;
|
||||
SEND("TIMEus=");
|
||||
SEND(u2str(elapsed));
|
||||
SEND("\n");
|
||||
return ERR_AMOUNT;
|
||||
}
|
||||
|
||||
|
||||
// test CORDIC function
|
||||
static errcodes_t cmd_testc(const char*, char *args){
|
||||
const char *fname = parse_func_name(args);
|
||||
if(!fname) return ERR_BADPAR;
|
||||
uint32_t elapsed = 0;
|
||||
bool ok = true;
|
||||
if(strcmp(fname, "sincos") == 0){
|
||||
elapsed = test_cordic_sincos();
|
||||
}
|
||||
else if(strcmp(fname, "sin") == 0){
|
||||
elapsed = test_cordic_sin();
|
||||
}else if(strcmp(fname, "cos") == 0){
|
||||
elapsed = test_cordic_cos();
|
||||
}else if(strcmp(fname, "atan") == 0){
|
||||
elapsed = test_cordic_atan();
|
||||
}else if(strcmp(fname, "sqrt") == 0){
|
||||
elapsed = test_cordic_sqrt();
|
||||
}else if(strcmp(fname, "log") == 0){
|
||||
elapsed = test_cordic_log();
|
||||
}else{
|
||||
ok = false;
|
||||
}
|
||||
if(!ok) return ERR_BADVAL;
|
||||
SEND("TIMEus=");
|
||||
SEND(u2str(elapsed));
|
||||
SEND("\n");
|
||||
return ERR_AMOUNT;
|
||||
}
|
||||
|
||||
static errcodes_t cmd_time(const char *, char *args){
|
||||
char *setter = splitargs(args, NULL);
|
||||
if(!setter) return ERR_BADVAL;
|
||||
uint32_t unix_time;
|
||||
if(setter == getnum(setter, &unix_time)) return ERR_BADVAL;
|
||||
float mjd = MJD_from_unix(unix_time);
|
||||
SEND("MJD="); SEND(float2str(mjd, 7));
|
||||
SEND("\nLST="); SEND(float2str(LST_from_unix(unix_time), 7));
|
||||
SEND("\n");
|
||||
return ERR_AMOUNT;
|
||||
}
|
||||
|
||||
static errcodes_t cmd_sets(const char *cmd, char *args){
|
||||
int32_t val;
|
||||
if(argsvals(args, NULL, &val)) set_sincos(val);
|
||||
CMDEQ();
|
||||
if(get_sincos()) SEND("CORDIC\n");
|
||||
else SEND("MATH\n");
|
||||
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;
|
||||
}
|
||||
53
G4:G431/CORDIC/commproto.h
Normal file
53
G4:G431/CORDIC/commproto.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* This file is part of the cordic 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/TODO " 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);
|
||||
BIN
G4:G431/CORDIC/cordic.bin
Executable file
BIN
G4:G431/CORDIC/cordic.bin
Executable file
Binary file not shown.
151
G4:G431/CORDIC/cordic.c
Normal file
151
G4:G431/CORDIC/cordic.c
Normal file
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* This file is part of the cordic 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 <stm32g4.h>
|
||||
#include <math.h>
|
||||
#include "cordic.h"
|
||||
|
||||
// default CSR settings
|
||||
#define CORDIC_CSR_DEF (5 << CORDIC_CSR_PRECISION_Pos)
|
||||
|
||||
// enable CORDIC
|
||||
static void cordic_enable(void){
|
||||
RCC->AHB1ENR |= RCC_AHB1ENR_CORDICEN;
|
||||
__DSB();
|
||||
}
|
||||
|
||||
// Convert float to Q1.31 in [-1, 1)
|
||||
static int32_t float_to_q31(float x){
|
||||
if(x >= 1.0f) x = 1.0f - 1e-6f;
|
||||
if(x <= -1.0f) x = -1.0f + 1e-6f;
|
||||
return (int32_t)(x * Q31_BASE);
|
||||
}
|
||||
|
||||
// Convert Q1.31 to float
|
||||
static float q31_to_float(int32_t q){
|
||||
return (float)q / Q31_BASE;
|
||||
}
|
||||
|
||||
static void cordic_init(void){
|
||||
static bool inited = false;
|
||||
if(!inited){
|
||||
cordic_enable();
|
||||
inited = true;
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
// Common function for one arg (sin, cos, sqrt, log)
|
||||
static float cordic_scalar(uint32_t func_mode, float x, int arg_count){
|
||||
cordic_init();
|
||||
CORDIC->CSR = (func_mode << CORDIC_CSR_FUNC_Pos) |
|
||||
CORDIC_CSR_INSIZE_1 | // in Q1.31
|
||||
CORDIC_CSR_OUTSIZE_0; // out Q1.31
|
||||
if(arg_count == 1){
|
||||
CORDIC->WDATA = float_to_q31(x);
|
||||
} else {
|
||||
...
|
||||
}
|
||||
cordic_wait_ready();
|
||||
int32_t res = CORDIC->RDATA;
|
||||
return q31_to_float(res);
|
||||
}
|
||||
#endif
|
||||
|
||||
// sincos
|
||||
void cordic_sincos(float angle, float *s, float *c){
|
||||
float norm = angle / M_PIf;
|
||||
if(norm > 1.0f) norm = 1.0f;
|
||||
if(norm < -1.0f) norm = -1.0f;
|
||||
cordic_init();
|
||||
CORDIC->CSR = (CORDIC_CSR_FUNC_SIN << CORDIC_CSR_FUNC_Pos) | CORDIC_CSR_DEF | CORDIC_CSR_NRES;
|
||||
CORDIC->WDATA = float_to_q31(norm);
|
||||
int32_t res = CORDIC->RDATA;
|
||||
if(s) *s = q31_to_float(res);
|
||||
res = CORDIC->RDATA;
|
||||
if(c) *c = q31_to_float(res);
|
||||
}
|
||||
|
||||
// sin
|
||||
float cordic_sin(float angle){
|
||||
float norm = angle / M_PIf;
|
||||
if(norm > 1.0f) norm = 1.0f;
|
||||
if(norm < -1.0f) norm = -1.0f;
|
||||
cordic_init();
|
||||
CORDIC->CSR = (CORDIC_CSR_FUNC_SIN << CORDIC_CSR_FUNC_Pos) | CORDIC_CSR_DEF;
|
||||
CORDIC->WDATA = float_to_q31(norm);
|
||||
int32_t res = CORDIC->RDATA;
|
||||
return q31_to_float(res);
|
||||
}
|
||||
|
||||
// cos
|
||||
float cordic_cos(float angle){
|
||||
float norm = angle / M_PIf;
|
||||
if(norm > 1.0f) norm = 1.0f;
|
||||
if(norm < -1.0f) norm = -1.0f;
|
||||
cordic_init();
|
||||
CORDIC->CSR = (CORDIC_CSR_FUNC_COS << CORDIC_CSR_FUNC_Pos) | CORDIC_CSR_DEF;
|
||||
CORDIC->WDATA = float_to_q31(norm);
|
||||
int32_t res = CORDIC->RDATA; // cos
|
||||
return q31_to_float(res);
|
||||
}
|
||||
|
||||
// atan2
|
||||
float cordic_atan(float val){
|
||||
if(val > 1.f || val < -1.f) return NAN;
|
||||
cordic_init();
|
||||
CORDIC->CSR = (CORDIC_CSR_FUNC_ATAN << CORDIC_CSR_FUNC_Pos) | CORDIC_CSR_DEF;
|
||||
CORDIC->WDATA = float_to_q31(val);
|
||||
int32_t res = CORDIC->RDATA;
|
||||
return q31_to_float(res) * M_PIf;
|
||||
}
|
||||
|
||||
// sqrt
|
||||
float cordic_sqrt(float x){
|
||||
if(x < 0.0f) x = 0.0f;
|
||||
float scale = 1.0f;
|
||||
if(x > 1.0f){
|
||||
if(x < 100.f){ scale = 10.f; x /= 100.f;}
|
||||
else if(x < 1e4f){ scale = 100.f; x /= 1e4f;}
|
||||
else if(x < 1e6f){ scale = 1e3f; x /= 1e6f;}
|
||||
else if(x < 1e10f){ scale = 1e5f; x /= 1e10f;}
|
||||
else return NAN; // number is too big
|
||||
}
|
||||
cordic_init();
|
||||
CORDIC->CSR = (CORDIC_CSR_FUNC_SQRT << CORDIC_CSR_FUNC_Pos) | CORDIC_CSR_DEF;
|
||||
CORDIC->WDATA = float_to_q31(x);
|
||||
int32_t res = CORDIC->RDATA;
|
||||
return scale * q31_to_float(res);
|
||||
}
|
||||
|
||||
// log
|
||||
float cordic_log(float x){
|
||||
if(x <= 0.0f) x = 0.00001f;
|
||||
float add = 0.f;
|
||||
if(x > 1.0f){
|
||||
if(x < 1000.f){ add = 6.907755279f; x /= 1000.f; }
|
||||
else if(x < 1e6f){ add = 13.815510558f; x /= 1e6f; }
|
||||
else if(x < 1e12f){ add = 27.63102112f; x /= 1e12f; }
|
||||
else return NAN;
|
||||
}
|
||||
cordic_init();
|
||||
CORDIC->CSR = (CORDIC_CSR_FUNC_LOG << CORDIC_CSR_FUNC_Pos) | CORDIC_CSR_DEF;
|
||||
CORDIC->WDATA = float_to_q31(x);
|
||||
int32_t res = CORDIC->RDATA;
|
||||
return q31_to_float(res) * 0.6931471805599453f + add;
|
||||
}
|
||||
1
G4:G431/CORDIC/cordic.cflags
Normal file
1
G4:G431/CORDIC/cordic.cflags
Normal file
@@ -0,0 +1 @@
|
||||
-std=c23
|
||||
3
G4:G431/CORDIC/cordic.config
Normal file
3
G4:G431/CORDIC/cordic.config
Normal file
@@ -0,0 +1,3 @@
|
||||
#define EBUG
|
||||
#define STM32G4
|
||||
#define STM32G431xx
|
||||
1
G4:G431/CORDIC/cordic.creator
Normal file
1
G4:G431/CORDIC/cordic.creator
Normal file
@@ -0,0 +1 @@
|
||||
[General]
|
||||
1
G4:G431/CORDIC/cordic.cxxflags
Normal file
1
G4:G431/CORDIC/cordic.cxxflags
Normal file
@@ -0,0 +1 @@
|
||||
-std=c++23
|
||||
21
G4:G431/CORDIC/cordic.files
Normal file
21
G4:G431/CORDIC/cordic.files
Normal file
@@ -0,0 +1,21 @@
|
||||
astro.c
|
||||
astro.h
|
||||
commproto.cpp
|
||||
commproto.h
|
||||
cordic.c
|
||||
cordic.h
|
||||
flash.c
|
||||
flash.h
|
||||
hardware.c
|
||||
hardware.h
|
||||
main.c
|
||||
ringbuffer.c
|
||||
ringbuffer.h
|
||||
servo.c
|
||||
servo.h
|
||||
strfunc.c
|
||||
strfunc.h
|
||||
test.c
|
||||
test.h
|
||||
usart.c
|
||||
usart.h
|
||||
47
G4:G431/CORDIC/cordic.h
Normal file
47
G4:G431/CORDIC/cordic.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* This file is part of the cordic 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>
|
||||
|
||||
#ifndef M_PIf
|
||||
#define M_PIf 3.141592653589793f
|
||||
#endif
|
||||
|
||||
#define Q31_BASE 2147483648.0f
|
||||
|
||||
// functions
|
||||
enum {
|
||||
CORDIC_CSR_FUNC_COS = 0,
|
||||
CORDIC_CSR_FUNC_SIN,
|
||||
CORDIC_CSR_FUNC_PHASE,
|
||||
CORDIC_CSR_FUNC_MOD,
|
||||
CORDIC_CSR_FUNC_ATAN,
|
||||
CORDIC_CSR_FUNC_COSH,
|
||||
CORDIC_CSR_FUNC_ATANH,
|
||||
CORDIC_CSR_FUNC_LOG,
|
||||
CORDIC_CSR_FUNC_SQRT
|
||||
};
|
||||
|
||||
void cordic_sincos(float angle, float *s, float *c);
|
||||
float cordic_sin(float angle);
|
||||
float cordic_cos(float angle);
|
||||
float cordic_atan(float val);
|
||||
float cordic_sqrt(float x);
|
||||
float cordic_log(float x);
|
||||
4
G4:G431/CORDIC/cordic.includes
Normal file
4
G4:G431/CORDIC/cordic.includes
Normal file
@@ -0,0 +1,4 @@
|
||||
.
|
||||
../inc
|
||||
../inc/Fx
|
||||
../inc/cm
|
||||
69
G4:G431/CORDIC/hardware.c
Normal file
69
G4:G431/CORDIC/hardware.c
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* This file is part of the cordic 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 <stm32g4.h>
|
||||
|
||||
#include "hardware.h"
|
||||
|
||||
volatile uint32_t Tms = 0;
|
||||
|
||||
/* Called when systick fires */
|
||||
void sys_tick_handler(){
|
||||
++Tms;
|
||||
}
|
||||
|
||||
static void timer_setup(){
|
||||
RCC->APB1ENR1 |= RCC_APB1ENR1_TIM3EN;
|
||||
__DSB();
|
||||
TIM3->CR1 = 0; // disable counter
|
||||
TIM3->PSC = 84; // 85 MHz / 85 = 1 MHz
|
||||
TIM3->ARR = 0xFFFFFFFF; // 32-bit auto-reload (maximum)
|
||||
}
|
||||
|
||||
void timer_start(){
|
||||
TIM3->CNT = 0;
|
||||
TIM3->CR1 = TIM_CR1_CEN;
|
||||
}
|
||||
|
||||
void timer_stop(){
|
||||
TIM3->CR1 = 0;
|
||||
}
|
||||
|
||||
uint32_t timer_read(){
|
||||
return TIM3->CNT;
|
||||
}
|
||||
|
||||
void gpio_setup(){
|
||||
RCC->AHB2ENR = RCC_AHB2ENR_GPIOAEN | RCC_AHB2ENR_GPIOCEN;
|
||||
__DSB();
|
||||
// PC6 as output, PC13 as input with pulldown
|
||||
GPIOC->MODER = (0xffffffff & ~(GPIO_MODER_MODE6 | GPIO_MODER_MODE13)) | MODER_O(6) | MODER_I(13);
|
||||
GPIOC->PUPDR = PUPD_PD(13);
|
||||
GPIOC->OTYPER = OTYPER_PP(6);
|
||||
// PA9 (Tx), PA10 (Rx) as alternate functions (for USART1)
|
||||
GPIOA->MODER = (0xabffffff & ~(GPIO_MODER_MODE9 | GPIO_MODER_MODE10)) |
|
||||
MODER_AF(9) | MODER_AF(10);
|
||||
GPIOA->AFR[1] = AFRf(7, 9) | AFRf(7, 10);
|
||||
// count milliseconds
|
||||
SysTick_Config(SysFreq / 1000);
|
||||
|
||||
// Setup TIM3 for microsecond counting
|
||||
timer_setup();
|
||||
}
|
||||
|
||||
|
||||
43
G4:G431/CORDIC/hardware.h
Normal file
43
G4:G431/CORDIC/hardware.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* This file is part of the cordic 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
|
||||
|
||||
// KEY (intpullup->0) - PC13
|
||||
// LED - PC6
|
||||
#define KEY_PORT GPIOC
|
||||
#define KEY_PIN (1<<13)
|
||||
#define LED_PORT GPIOC
|
||||
#define LED_PIN (1<<6)
|
||||
#define KEY_PRESSED() (pin_read(KEY_PORT, KEY_PIN) == 1)
|
||||
#define LED_ON() do{pin_set(LED_PORT, LED_PIN);}while(0)
|
||||
#define LED_OFF() do{pin_clear(LED_PORT, LED_PIN);}while(0)
|
||||
#define LED_TOGG() do{pin_toggle(LED_PORT, LED_PIN);}while(0)
|
||||
|
||||
#define SERVO_AMOUNT 4
|
||||
|
||||
#define MIN_USART_SPEED 1200
|
||||
#define MAX_USART_SPEED 3000000
|
||||
|
||||
extern volatile uint32_t Tms;
|
||||
|
||||
void gpio_setup();
|
||||
|
||||
void timer_start();
|
||||
void timer_stop();
|
||||
uint32_t timer_read();
|
||||
71
G4:G431/CORDIC/main.c
Normal file
71
G4:G431/CORDIC/main.c
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* This file is part of the cordic project.
|
||||
* 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 <stm32g4.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "commproto.h"
|
||||
#include "hardware.h"
|
||||
#include "strfunc.h"
|
||||
#include "usart.h"
|
||||
|
||||
// 40ms for debouncing
|
||||
#define TDEBOUNCE (40)
|
||||
|
||||
int main(void){
|
||||
StartHSE();
|
||||
// init storage first to take base values
|
||||
gpio_setup();
|
||||
usart_setup(115200);
|
||||
bool pressed = false;
|
||||
uint32_t Tblink = 0, Tkey = 0; // blink and key pressed time
|
||||
uint32_t Tpressed = 0;
|
||||
while(1){
|
||||
USART_flags_t f = usart_process();
|
||||
if(f.rxovrfl) usart_sendstr("Rx buffer overflow!\n");
|
||||
if(f.txerr) usart_sendstr("Tx error!\n");
|
||||
char *str = usart_getline();
|
||||
if(str){
|
||||
const char *ans = parse_cmd(usart_sendstr, str);
|
||||
if(ans) usart_sendstr(ans);
|
||||
}
|
||||
if(Tms - Tblink > 499){
|
||||
LED_TOGG();
|
||||
Tblink = Tms;
|
||||
}
|
||||
if(KEY_PRESSED()){
|
||||
if(!pressed){ // new event
|
||||
pressed = true;
|
||||
usart_sendstr("Key pressed\n");
|
||||
timer_start();
|
||||
Tpressed = Tms;
|
||||
}
|
||||
Tkey = Tms;
|
||||
}else{ // key released
|
||||
if(pressed && (Tms - Tkey) > TDEBOUNCE){ // wait for debounce
|
||||
timer_stop();
|
||||
uint32_t micros = timer_read();
|
||||
uint32_t millis = Tms - Tpressed;
|
||||
pressed = false;
|
||||
usart_sendstr("Key released\n");
|
||||
usart_sendstr("Tms="); usart_sendstr(u2str(millis));
|
||||
usart_sendstr("\nTus="); usart_sendstr(u2str(micros)); usart_sendstr("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
4
G4:G431/CORDIC/openocd.cfg
Normal file
4
G4:G431/CORDIC/openocd.cfg
Normal file
@@ -0,0 +1,4 @@
|
||||
set FLASH_SIZE 0x20000
|
||||
|
||||
source [find interface/stlink-v2-1.cfg]
|
||||
source [find target/stm32g4x.cfg]
|
||||
203
G4:G431/CORDIC/ringbuffer.c
Normal file
203
G4:G431/CORDIC/ringbuffer.c
Normal 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 <stm32g4.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 = true;
|
||||
int l = datalen(b);
|
||||
b->busy = false;
|
||||
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 = true;
|
||||
int ret = hasbyte(b, byte);
|
||||
b->busy = false;
|
||||
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 = true;
|
||||
int r = read(b, s, len);
|
||||
b->busy = false;
|
||||
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 = true;
|
||||
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 = false;
|
||||
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 = true;
|
||||
int n = lento(b, byte);
|
||||
b->busy = false;
|
||||
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 = true;
|
||||
int w = write(b, str, l);
|
||||
b->busy = false;
|
||||
return w;
|
||||
}
|
||||
|
||||
// just delete all information in buffer `b`
|
||||
int RB_clearbuf(ringbuffer *b){
|
||||
CHK(b);
|
||||
if(b->busy) return -1;
|
||||
b->busy = true;
|
||||
b->head = 0;
|
||||
b->tail = 0;
|
||||
memset(b->data, 0, b->length);
|
||||
b->busy = false;
|
||||
return 1;
|
||||
}
|
||||
36
G4:G431/CORDIC/ringbuffer.h
Normal file
36
G4:G431/CORDIC/ringbuffer.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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>
|
||||
|
||||
typedef struct{
|
||||
uint8_t *data; // data buffer
|
||||
const int length; // its length
|
||||
int head; // head index
|
||||
int tail; // tail index
|
||||
volatile bool 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);
|
||||
378
G4:G431/CORDIC/strfunc.c
Normal file
378
G4:G431/CORDIC/strfunc.c
Normal file
@@ -0,0 +1,378 @@
|
||||
/*
|
||||
* 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> // isnan/isinf
|
||||
#include <stm32g4.h>
|
||||
#include <string.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;
|
||||
}
|
||||
|
||||
// be careful: if pow10 would be bigger you should change str[] size!
|
||||
static const float pwr10[] = {1.f, 10.f, 100.f, 1000.f, 10000.f, 100000.f, 1000000.f, 10000000.f};
|
||||
static const float rounds[] = {0.5f, 0.05f, 0.005f, 0.0005f, 0.00005f, 0.000005f, 0.0000005f, 0.00000005f};
|
||||
#define P10L (sizeof(pwr10)/sizeof(float) - 1)
|
||||
char *float2str(float x, uint8_t prec){
|
||||
static char str[16] = {0}; // -117.5494E-36\0 - 14 symbols max!
|
||||
if(prec > P10L) prec = P10L;
|
||||
if(isnan(x)){ memcpy(str, "NAN", 4); return str;}
|
||||
else{
|
||||
int i = isinf(x);
|
||||
if(i){memcpy(str, "-INF", 5); if(i == 1) return str+1; else return str;}
|
||||
}
|
||||
char *s = str + 14; // go to end of buffer
|
||||
uint8_t minus = 0;
|
||||
if(x < 0){
|
||||
x = -x;
|
||||
minus = 1;
|
||||
}
|
||||
int pow = 0; // xxxEpow
|
||||
// now convert float to 1.xxxE3y
|
||||
while(x > 1000.f){
|
||||
x /= 1000.f;
|
||||
pow += 3;
|
||||
}
|
||||
if(x > 0.) while(x < 1.){
|
||||
x *= 1000.f;
|
||||
pow -= 3;
|
||||
}
|
||||
// print Eyy
|
||||
if(pow){
|
||||
uint8_t m = 0;
|
||||
if(pow < 0){pow = -pow; m = 1;}
|
||||
while(pow){
|
||||
int p10 = pow/10;
|
||||
*s-- = '0' + (pow - 10*p10);
|
||||
pow = p10;
|
||||
}
|
||||
if(m) *s-- = '-';
|
||||
*s-- = 'E';
|
||||
}
|
||||
// now our number is in [1, 1000]
|
||||
uint32_t units;
|
||||
if(prec){
|
||||
units = (uint32_t) x;
|
||||
uint32_t decimals = (uint32_t)((x-units+rounds[prec])*pwr10[prec]);
|
||||
// print decimals
|
||||
while(prec){
|
||||
int d10 = decimals / 10;
|
||||
*s-- = '0' + (decimals - 10*d10);
|
||||
decimals = d10;
|
||||
--prec;
|
||||
}
|
||||
// decimal point
|
||||
*s-- = '.';
|
||||
}else{ // without decimal part
|
||||
units = (uint32_t) (x + 0.5);
|
||||
}
|
||||
// print main units
|
||||
if(units == 0) *s-- = '0';
|
||||
else while(units){
|
||||
uint32_t u10 = units / 10;
|
||||
*s-- = '0' + (units - 10*u10);
|
||||
units = u10;
|
||||
}
|
||||
if(minus) *s-- = '-';
|
||||
return s+1;
|
||||
}
|
||||
|
||||
char *getfloat(char *str, float *f){
|
||||
str = omit_spaces(str);
|
||||
int isminus = 0;
|
||||
if(*str == '-'){
|
||||
isminus = 1;
|
||||
++str;
|
||||
}else if (*str == '+'){
|
||||
++str;
|
||||
}
|
||||
float result = 0.0f;
|
||||
while(*str >= '0' && *str <= '9'){
|
||||
result = result * 10.0f + (float)(*str - '0');
|
||||
++str;
|
||||
}
|
||||
if(*str != '.') goto retn;
|
||||
|
||||
++str; // omit point
|
||||
float frac = 0.0f;
|
||||
float divisor = 1.0f;
|
||||
|
||||
while(*str >= '0' && *str <= '9'){
|
||||
divisor *= 10.0f;
|
||||
frac = frac * 10.0f + (float)(*str - '0');
|
||||
++str;
|
||||
}
|
||||
|
||||
if(divisor > 1.0f){
|
||||
result += frac / divisor;
|
||||
}
|
||||
|
||||
if(*str == 'e' || *str == 'E'){ // exp
|
||||
++str;
|
||||
int32_t E;
|
||||
if(str != getint(str, &E)){
|
||||
if(E > 0) while(E-- > 0) result *= 10.f;
|
||||
else while(E++ < 0) result /= 10.f;
|
||||
}
|
||||
}
|
||||
|
||||
retn:
|
||||
if(f) *f = (isminus) ? -result : result;
|
||||
return str;
|
||||
}
|
||||
|
||||
31
G4:G431/CORDIC/strfunc.h
Normal file
31
G4:G431/CORDIC/strfunc.h
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
char *omit_spaces(char *buf);
|
||||
void hexdump(int (*sendfun)(const char*), uint8_t *arr, uint16_t len);
|
||||
char *u2str(uint32_t val);
|
||||
char *i2str(int32_t i);
|
||||
char *float2str(float x, uint8_t prec);
|
||||
char *uhex2str(uint32_t val);
|
||||
char *gethex(const char *buf, uint32_t *N);
|
||||
char *getnum(char *txt, uint32_t *N);
|
||||
char *getint(char *txt, int32_t *I);
|
||||
char *getfloat(char *str, float *f);
|
||||
127
G4:G431/CORDIC/test.c
Normal file
127
G4:G431/CORDIC/test.c
Normal file
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* This file is part of the cordic 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 <math.h>
|
||||
#include <stdint.h>
|
||||
#include <stm32g4.h>
|
||||
|
||||
#include "test.h"
|
||||
#include "hardware.h"
|
||||
#include "cordic.h"
|
||||
|
||||
// amount of iterations over test
|
||||
#define N_TESTS 1000
|
||||
|
||||
static float arr[N_TESTS];
|
||||
|
||||
|
||||
// RNG
|
||||
static uint32_t rand_state = 123456789;
|
||||
static uint32_t next_rand(){
|
||||
rand_state = rand_state * 1664525 + 1013904223;
|
||||
return rand_state;
|
||||
}
|
||||
|
||||
// fill array with random angles
|
||||
static void fill_random_sin_cos(){
|
||||
for(int i = 0; i < N_TESTS; ++i){
|
||||
// angle from -pi to +pi
|
||||
arr[i] = (float)(next_rand() % 62831853) / 10000000.0f - 3.14159265f;
|
||||
}
|
||||
}
|
||||
|
||||
static void fill_random_atan(){
|
||||
for(int i = 0; i < N_TESTS; ++i){
|
||||
arr[i] = (float)(next_rand() % 2000000) / 1e6f - 1e6f; // [-1,1]
|
||||
}
|
||||
}
|
||||
|
||||
static void fill_random_sqrt(){
|
||||
for(int i = 0; i < N_TESTS; ++i){
|
||||
arr[i] = (float)(next_rand() % 10000) / 100.0f; // [0,100]
|
||||
}
|
||||
}
|
||||
|
||||
static void fill_random_log(){
|
||||
for(int i = 0; i < N_TESTS; ++i){
|
||||
arr[i] = (float)(next_rand() % 10000 + 1) / 100.0f; // [0.01, 100]
|
||||
}
|
||||
}
|
||||
|
||||
// main test template
|
||||
static uint32_t run_test(void (*gen)(), float (*func)(float)){
|
||||
gen();
|
||||
volatile float result = 0.0f; // don't let gcc to optimize this cycle
|
||||
timer_start();
|
||||
for(int i = 0; i < N_TESTS; ++i){
|
||||
result = func(arr[i]);
|
||||
(void) result;
|
||||
}
|
||||
timer_stop();
|
||||
return timer_read();
|
||||
}
|
||||
|
||||
static uint32_t run_test2(void (*gen)(), void (*func)(float, float*, float*)){
|
||||
gen();
|
||||
volatile float result1 = 0.f, result2 = 0.f; // don't let gcc to optimize this cycle
|
||||
timer_start();
|
||||
for(int i = 0; i < N_TESTS; ++i){
|
||||
func(arr[i], (float*)&result1, (float*)&result2);
|
||||
(void) result1;
|
||||
(void) result2;
|
||||
}
|
||||
timer_stop();
|
||||
return timer_read();
|
||||
}
|
||||
|
||||
// ------------- math.h tests -------------
|
||||
uint32_t test_math_sin(){
|
||||
return run_test(fill_random_sin_cos, sinf);
|
||||
}
|
||||
uint32_t test_math_cos(){
|
||||
return run_test(fill_random_sin_cos, cosf);
|
||||
}
|
||||
uint32_t test_math_atan(){
|
||||
return run_test(fill_random_atan, atanf);
|
||||
}
|
||||
uint32_t test_math_sqrt(){
|
||||
return run_test(fill_random_sqrt, sqrtf);
|
||||
}
|
||||
uint32_t test_math_log(){
|
||||
return run_test(fill_random_log, logf);
|
||||
}
|
||||
|
||||
// ------------- CORDIC tests -------------
|
||||
uint32_t test_cordic_sincos(){
|
||||
return run_test2(fill_random_sin_cos, cordic_sincos);
|
||||
}
|
||||
uint32_t test_cordic_sin(){
|
||||
return run_test(fill_random_sin_cos, cordic_sin);
|
||||
}
|
||||
uint32_t test_cordic_cos(){
|
||||
return run_test(fill_random_sin_cos, cordic_cos);
|
||||
}
|
||||
uint32_t test_cordic_atan(){
|
||||
return run_test(fill_random_atan, cordic_atan);
|
||||
}
|
||||
uint32_t test_cordic_sqrt(){
|
||||
return run_test(fill_random_sqrt, cordic_sqrt);
|
||||
}
|
||||
uint32_t test_cordic_log(){
|
||||
return run_test(fill_random_log, cordic_log);
|
||||
}
|
||||
36
G4:G431/CORDIC/test.h
Normal file
36
G4:G431/CORDIC/test.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* This file is part of the cordic 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>
|
||||
|
||||
// libmath tests
|
||||
uint32_t test_math_sin();
|
||||
uint32_t test_math_cos();
|
||||
uint32_t test_math_atan();
|
||||
uint32_t test_math_sqrt();
|
||||
uint32_t test_math_log();
|
||||
|
||||
// CORDIC tests
|
||||
uint32_t test_cordic_sincos();
|
||||
uint32_t test_cordic_sin();
|
||||
uint32_t test_cordic_cos();
|
||||
uint32_t test_cordic_atan();
|
||||
uint32_t test_cordic_sqrt();
|
||||
uint32_t test_cordic_log();
|
||||
257
G4:G431/CORDIC/usart.c
Normal file
257
G4:G431/CORDIC/usart.c
Normal file
@@ -0,0 +1,257 @@
|
||||
/*
|
||||
* This file is part of the cordic 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 <stm32g4.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "hardware.h" // Tms
|
||||
#include "ringbuffer.h"
|
||||
#include "usart.h"
|
||||
|
||||
// USART for text-based protocol, each data portion ends with '\n'
|
||||
// RX works over circular DMA
|
||||
|
||||
// USART-depending part ------->
|
||||
// USART1 @ PA9 (Tx) - MUX25 - and PA10 (Rx) - MUX24
|
||||
// select USART and its DMA channels
|
||||
#define USARTx USART1
|
||||
#define USARTxAPB APB2ENR
|
||||
#define USARTxEN RCC_APB2ENR_USART1EN
|
||||
#define USART_APBEN RCC_APB2
|
||||
// DMAMUX channels: 24 - USART1Rx, 25 - USART1Tx
|
||||
#define DMAMUXRXN (24)
|
||||
#define DMAMUXTXN (25)
|
||||
// DMA channels: 1 (0 in MUX) - Rx, 2 (1 in MUX) - Tx; TC and error flags
|
||||
// use DMA ch2/3 because they both have single IRQ
|
||||
#define DMAx DMA1
|
||||
#define DMAxEN (RCC_AHB1ENR_DMA1EN | RCC_AHB1ENR_DMAMUX1EN)
|
||||
#define DMACHRX DMA1_Channel1
|
||||
#define DMARXTCF DMA_ISR_TCIF1
|
||||
#define DMARXEF DMA_ISR_TEIF1
|
||||
#define DMACHTX DMA1_Channel2
|
||||
#define DMATXTCF DMA_ISR_TCIF2
|
||||
#define DMATXEF DMA_ISR_TEIF2
|
||||
#define DMAMUXRX DMAMUX1_Channel0
|
||||
#define DMAMUXTX DMAMUX1_Channel1
|
||||
#define USARTIRQn USART1_IRQn
|
||||
#define DMARXIRQ DMA1_Channel1_IRQn
|
||||
#define DMATXIRQ DMA1_Channel2_IRQn
|
||||
|
||||
// interrupt aliases
|
||||
static void usart_isr();
|
||||
static void dmatx_isr();
|
||||
static void dmarx_isr();
|
||||
void usart1_isr() __attribute__ ((alias ("usart_isr")));
|
||||
void dma1_channel1_isr() __attribute__ ((alias ("dmarx_isr")));
|
||||
void dma1_channel2_isr() __attribute__ ((alias ("dmatx_isr")));
|
||||
// <-------- USART-depending part
|
||||
|
||||
// RX/TX DMA->CCR without EN flag
|
||||
#define DMARXCCR (DMA_CCR_MINC | DMA_CCR_CIRC | DMA_CCR_TEIE)
|
||||
#define DMATXCCR (DMA_CCR_MINC | DMA_CCR_DIR | DMA_CCR_TCIE | DMA_CCR_TEIE)
|
||||
|
||||
static volatile bool gotstring = true; // got '\n' in input stream -> force data reading to inbuf
|
||||
static volatile bool txrdy = true; // Tx DMA not busy
|
||||
static volatile USART_flags_t curflags; // current flags (cleared in `usart_process`)
|
||||
// rx/tx DMA buffers
|
||||
static uint8_t dmarxbuf[USARTRXDMABUFSZ];
|
||||
static uint8_t dmatxbuf[USARTTXDMABUFSZ];
|
||||
// index of last DMA read position
|
||||
static uint32_t dma_read_idx = 0;
|
||||
// for ringbuffer
|
||||
static uint8_t rbrxbuf[USARTRXBUFSZ], rbtxbuf[USARTTXBUFSZ];
|
||||
static ringbuffer TxRB = {.data = rbtxbuf, .length = USARTTXBUFSZ};
|
||||
static ringbuffer RxRB = {.data = rbrxbuf, .length = USARTRXBUFSZ};
|
||||
|
||||
#define USART_BRR(speed) ((SysFreq + (speed)/2) / (speed))
|
||||
|
||||
static void reinit_rx_dma(){
|
||||
dma_read_idx = 0;
|
||||
RB_clearbuf(&RxRB);
|
||||
DMACHRX->CCR = DMARXCCR; // stop to reload
|
||||
DMACHRX->CNDTR = USARTRXDMABUFSZ;
|
||||
DMACHRX->CMAR = (uint32_t) dmarxbuf;
|
||||
DMACHRX->CCR = DMARXCCR | DMA_CCR_EN;
|
||||
}
|
||||
|
||||
void usart_setup(uint32_t speed){
|
||||
RCC->AHB1ENR |= DMAxEN; // enable DMA
|
||||
// enable USART clocking
|
||||
RCC->USARTxAPB |= USARTxEN;
|
||||
// baudrate
|
||||
USARTx->BRR = USART_BRR(speed);
|
||||
// eol character: '/n'
|
||||
USARTx->CR2 = USART_CR2_ADD_VAL('\n');
|
||||
// enable DMA transmission
|
||||
USARTx->CR3 = USART_CR3_DMAT | USART_CR3_DMAR;
|
||||
// set up DMA channels
|
||||
// Tx channel: mem++, mem->periph, 8bit, compl.&err. irq
|
||||
DMACHTX->CCR = DMATXCCR;
|
||||
DMACHTX->CPAR = (uint32_t) &USARTx->TDR; // peripherial address
|
||||
// Rx channel: mem++, periph->mem, 8bit, compl.&err. irq
|
||||
DMACHRX->CCR = DMARXCCR;
|
||||
DMACHRX->CPAR = (uint32_t) &USARTx->RDR; // peripherial address
|
||||
// set up DMAMUX channels
|
||||
// enumeration of DMAMUX starts from 0 (DMA - from 1)!
|
||||
DMAMUXRX->CCR = DMAMUXRXN;
|
||||
DMAMUXTX->CCR = DMAMUXTXN;
|
||||
// charmatch interrupt, enable transmitter and receiver, enable usart
|
||||
USARTx->CR1 = USART_CR1_CMIE | USART_CR1_TE | USART_CR1_RE | USART_CR1_UE;
|
||||
USARTx->ICR = 0xffffffff; // clear all flags
|
||||
reinit_rx_dma();
|
||||
NVIC_EnableIRQ(USARTIRQn);
|
||||
NVIC_EnableIRQ(DMARXIRQ);
|
||||
NVIC_EnableIRQ(DMATXIRQ);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief usart_sendbuf - send next data portion
|
||||
* @return true if sent something
|
||||
*/
|
||||
static bool usart_sendbuf(){
|
||||
if(!txrdy) return false;
|
||||
int rd = RB_read(&TxRB, dmatxbuf, USARTTXDMABUFSZ);
|
||||
if(rd < 1) return false; // nothing to write or busy
|
||||
// set up DMA
|
||||
DMACHTX->CCR = DMATXCCR;
|
||||
DMACHTX->CMAR = (uint32_t) dmatxbuf;
|
||||
DMACHTX->CNDTR = rd;
|
||||
USARTx->ICR = USART_ICR_TCCF; // clear TC flag
|
||||
txrdy = false;
|
||||
// activate DMA
|
||||
DMACHTX->CCR = DMATXCCR | DMA_CCR_EN;
|
||||
return true;
|
||||
}
|
||||
|
||||
int usart_send(const char *str, int len){
|
||||
if(!str || len < 1) return 0;
|
||||
uint32_t t = Tms;
|
||||
int sent = 0;
|
||||
do{
|
||||
IWDG->KR = IWDG_REFRESH;
|
||||
int put = RB_write(&TxRB, (uint8_t*)str, len);
|
||||
if(put < 0) continue; // busy
|
||||
else if(put == 0){
|
||||
usart_sendbuf(); // no place
|
||||
t = Tms;
|
||||
}else{
|
||||
len -= put;
|
||||
sent += put;
|
||||
str += put;
|
||||
}
|
||||
}while(len && (Tms - t) < USARTBLKTMOUT); // not more than `block` ms!
|
||||
return sent;
|
||||
}
|
||||
|
||||
int usart_sendstr(const char *str){
|
||||
int l = strlen(str);
|
||||
return usart_send(str, l);
|
||||
}
|
||||
|
||||
static void addtoreadidx(int adder){
|
||||
dma_read_idx += adder;
|
||||
if(dma_read_idx >= USARTRXDMABUFSZ) dma_read_idx -= USARTRXDMABUFSZ;
|
||||
}
|
||||
|
||||
// return current flags
|
||||
USART_flags_t usart_process(){
|
||||
static uint32_t Tlast = 0;
|
||||
USART_flags_t flags = curflags;
|
||||
curflags.all = 0;
|
||||
if(RB_datalento(&TxRB, '\n') > 1 || Tms - Tlast >= USARTSENDTMOUT){ // send buffer as we found '\n' or each 10ms
|
||||
if(usart_sendbuf()) Tlast = Tms;
|
||||
}
|
||||
int remained = DMACHRX->CNDTR;
|
||||
int write_idx = USARTRXDMABUFSZ - remained; // next symbol to be written
|
||||
int available = (write_idx - dma_read_idx); // length of data available
|
||||
if(available < 0) available += USARTRXDMABUFSZ; // write to the left of read
|
||||
if(available == 0) return flags;
|
||||
// add next data portion to RX ring buffer
|
||||
if(available >= (USARTRXDMABUFSZ / 2) || gotstring){
|
||||
// copy data in one or two chunks (wrap handling)
|
||||
// check if we can write to RB `available` bytes
|
||||
int rballow = RxRB.length - 1 - RB_datalen(&RxRB);
|
||||
if(rballow < available){
|
||||
if(available > USARTRXDMABUFSZ - 2){ // near overfull
|
||||
flags.rxovrfl = 1;
|
||||
reinit_rx_dma();
|
||||
RB_clearbuf(&RxRB);
|
||||
return flags;
|
||||
}
|
||||
if(rballow < 1) return flags;
|
||||
available = rballow; // read at least as we can
|
||||
}
|
||||
if(dma_read_idx + available <= USARTRXDMABUFSZ){ // head before tail
|
||||
int written = RB_write(&RxRB, &dmarxbuf[dma_read_idx], available);
|
||||
if(written == available && dmarxbuf[dma_read_idx+available-1] == '\n') gotstring = 0;
|
||||
if(written > 0) addtoreadidx(written);
|
||||
}else{ // head after tail - two chunks
|
||||
int first = USARTRXDMABUFSZ - dma_read_idx;
|
||||
int written = RB_write(&RxRB, &dmarxbuf[dma_read_idx], first);
|
||||
if(written != first){ // could write only part - just increase read index
|
||||
if(written > 0) addtoreadidx(written);
|
||||
}else{
|
||||
dma_read_idx = 0;
|
||||
int last = available - first;
|
||||
written = RB_write(&RxRB, dmarxbuf, last);
|
||||
if(written == last && dmarxbuf[last-1] == '\n') gotstring = 0;
|
||||
if(written > 0) addtoreadidx(written);
|
||||
}
|
||||
}
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
char *usart_getline(){
|
||||
static char buff[MAX_INPLEN];
|
||||
int l = RB_datalento(&RxRB, '\n');
|
||||
if(l < 1){
|
||||
l = RB_datalen(&RxRB); // Rx ringbuffer could be near overflow but without '\n'
|
||||
if(l < MAX_INPLEN-1) return NULL; // allow to wait for last symbols
|
||||
}
|
||||
if(l > MAX_INPLEN-1){ // overflow -> read at least part of the string
|
||||
l = MAX_INPLEN-1;
|
||||
}
|
||||
if(l != RB_read(&RxRB, (uint8_t*)buff, l)) return NULL;
|
||||
buff[l] = 0; // return with '\n' at end of line (so user can detect non-finished overflowed lines)
|
||||
return buff;
|
||||
}
|
||||
|
||||
// interrupt by '\n'
|
||||
static void usart_isr(){
|
||||
if(USARTx->ISR & USART_ISR_CMF){ // got '\n' @ USARTx
|
||||
gotstring = true;
|
||||
}
|
||||
USARTx->ICR = 0xffffffff; // clear all flags
|
||||
}
|
||||
|
||||
static void dmarx_isr(){
|
||||
volatile uint32_t isr = DMAx->ISR;
|
||||
if(isr & DMARXEF){ // error
|
||||
reinit_rx_dma();
|
||||
curflags.rxovrfl = 1;
|
||||
}
|
||||
DMAx->IFCR = DMARXEF; // clear all flags
|
||||
}
|
||||
|
||||
static void dmatx_isr(){
|
||||
volatile uint32_t isr = DMAx->ISR;
|
||||
if(isr & DMATXTCF) txrdy = true;
|
||||
if(isr & DMATXEF) curflags.txerr = 1;
|
||||
DMAx->IFCR = DMATXTCF | DMATXEF; // clear all flags
|
||||
}
|
||||
49
G4:G431/CORDIC/usart.h
Normal file
49
G4:G431/CORDIC/usart.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* This file is part of the cordic 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>
|
||||
|
||||
// maximal length of input string (including '\n' and terminating zero)
|
||||
#define MAX_INPLEN (128)
|
||||
// Rx/Tx ring buffer size
|
||||
#define USARTTXBUFSZ (512)
|
||||
#define USARTRXBUFSZ (512)
|
||||
// Rx/Tx DMA buffer size
|
||||
#define USARTTXDMABUFSZ (256)
|
||||
#define USARTRXDMABUFSZ (256)
|
||||
|
||||
// blocking timeout - not more than 5ms
|
||||
#define USARTBLKTMOUT (5)
|
||||
// send buffer each 10ms
|
||||
#define USARTSENDTMOUT (10)
|
||||
|
||||
typedef union{
|
||||
struct{
|
||||
uint8_t txerr : 1; // transmit error
|
||||
uint8_t rxovrfl : 1; // receive buffer overflow
|
||||
};
|
||||
uint8_t all;
|
||||
} USART_flags_t;
|
||||
|
||||
void usart_setup(uint32_t speed);
|
||||
int usart_send(const char *str, int len);
|
||||
char *usart_getline();
|
||||
int usart_sendstr(const char *str);
|
||||
USART_flags_t usart_process();
|
||||
2
G4:G431/CORDIC/version.inc
Normal file
2
G4:G431/CORDIC/version.inc
Normal file
@@ -0,0 +1,2 @@
|
||||
#define BUILD_NUMBER "48"
|
||||
#define BUILD_DATE "2026-08-12"
|
||||
12
G4:G431/Servo/Makefile
Normal file
12
G4:G431/Servo/Makefile
Normal file
@@ -0,0 +1,12 @@
|
||||
BINARY := servo
|
||||
PREFIX := /usr/bin/arm-none-eabi
|
||||
CFLAGS = -std=c23
|
||||
CPPFLAGS = -std=c++11
|
||||
# MCU code
|
||||
MCU := G431xx
|
||||
# change this linking script depending on particular MCU model,
|
||||
LDSCRIPT := stm32g431xb.ld
|
||||
|
||||
include ../makefile.g4
|
||||
include ../../makefile.stm32
|
||||
|
||||
344
G4:G431/Servo/commproto.cpp
Normal file
344
G4:G431/Servo/commproto.cpp
Normal file
@@ -0,0 +1,344 @@
|
||||
/*
|
||||
* 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 <stm32g4.h>
|
||||
|
||||
#include "commproto.h"
|
||||
#include "flash.h"
|
||||
#include "hardware.h"
|
||||
#include "servo.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)")
|
||||
|
||||
#define STR_HELPER(x) #x
|
||||
#define STR(x) STR_HELPER(x)
|
||||
|
||||
// list of all commands and handlers
|
||||
#define COMMAND_TABLE \
|
||||
COMMAND(dumpconf, "dump current config") \
|
||||
COMMAND(eraseflash, "erase full flash storage") \
|
||||
COMMAND(help, "show this help") \
|
||||
COMMAND(maxpos, "servoN maximal allowed position (conf)") \
|
||||
COMMAND(maxspeed, "servoN maximal allowed speed (from 1 to " STR(SERVO_MAXSPEED) ", conf)") \
|
||||
COMMAND(mcureset, "reset MCU") \
|
||||
COMMAND(minpos, "servoN minimal allowed position (conf)") \
|
||||
COMMAND(readconf, "re-read config from flash") \
|
||||
COMMAND(saveconf, "save config to flash") \
|
||||
COMMAND(servo, "servoN value, mks (in allowed range)") \
|
||||
COMMAND(servopos, "servoN target position with given speed") \
|
||||
COMMAND(servospeed, "servoN speed, mks per 20ms (1..maxspeed)") \
|
||||
COMMAND(startpos, "servoN started position (from " STR(SERVO_MINPULSE) " to " STR(SERVO_MAXPULSE) ", conf)") \
|
||||
COMMAND(time, "show current time (ms)") \
|
||||
COMMAND(usart_speed,"speed of USART (set after reset, conf)") \
|
||||
|
||||
|
||||
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)
|
||||
// the same as last but with command as option and uint value
|
||||
#define SHOWPARU(cmd, x, val) do{SEND(cmd); SEND(u2str((uint32_t)x)); SEND(EQ); SEND(u2str(val));}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){
|
||||
if(parval) *parval = I32;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static errcodes_t cmd_servo(const char *cmd, char *args){
|
||||
int32_t val, parno;
|
||||
if(argsvals(args, &parno, &val)){ // setter
|
||||
if(parno < 0 || parno >= SERVO_AMOUNT) return ERR_BADPAR;
|
||||
if(!set_servo((uint8_t)parno, (uint16_t)val)) return ERR_BADVAL;
|
||||
}
|
||||
uint16_t curval;
|
||||
if(!get_servo((uint8_t)parno, &curval)) return ERR_BADPAR;
|
||||
CMDEQP(parno);
|
||||
SEND(u2str(curval));
|
||||
SEND("\n");
|
||||
return ERR_AMOUNT;
|
||||
}
|
||||
|
||||
static errcodes_t cmd_servopos(const char *cmd, char *args){
|
||||
int32_t val, parno;
|
||||
if(argsvals(args, &parno, &val)){ // setter
|
||||
if(parno < 0 || parno >= SERVO_AMOUNT) return ERR_BADPAR;
|
||||
if(!servo_set_tagpos((uint8_t)parno, (uint16_t)val)) return ERR_BADVAL;
|
||||
}
|
||||
uint16_t curval;
|
||||
if(!servo_get_tagpos((uint8_t)parno, &curval)) return ERR_BADPAR;
|
||||
CMDEQP(parno);
|
||||
SEND(u2str(curval));
|
||||
SEND("\n");
|
||||
return ERR_AMOUNT;
|
||||
}
|
||||
|
||||
static errcodes_t cmd_servospeed(const char *cmd, char *args){
|
||||
int32_t val, parno;
|
||||
if(argsvals(args, &parno, &val)){ // setter
|
||||
if(parno < 0 || parno >= SERVO_AMOUNT) return ERR_BADPAR;
|
||||
if(!servo_set_speed((uint8_t)parno, (uint16_t)val)) return ERR_BADVAL;
|
||||
}
|
||||
uint16_t curval;
|
||||
if(!servo_get_speed((uint8_t)parno, &curval)) return ERR_BADPAR;
|
||||
CMDEQP(parno);
|
||||
SEND(u2str(curval));
|
||||
SEND("\n");
|
||||
return ERR_AMOUNT;
|
||||
}
|
||||
|
||||
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()) return ERR_CANTRUN;
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
static errcodes_t cmd_readconf(const char*, char*){
|
||||
flashstorage_init();
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
static errcodes_t cmd_dumpconf(const char*, char*){
|
||||
SEND("userconf_sz="); SEND(u2str(the_conf.userconf_sz));
|
||||
SEND("\ncurr_idx="); SEND(i2str(currentconfidx));
|
||||
SEND("\ncapacity="); SEND(u2str(maxCnum-2));
|
||||
SEND("\nusart_speed="); SEND(u2str(the_conf.usart_speed));
|
||||
for(int i = 0; i < SERVO_AMOUNT ; ++i){
|
||||
SHOWPARU("\nstartpos", i, the_conf.startpulse[i]);
|
||||
SHOWPARU("\nminpos", i, the_conf.minpulse[i]);
|
||||
SHOWPARU("\nmaxpos", i, the_conf.maxpulse[i]);
|
||||
SHOWPARU("\nmaxspeed", i, the_conf.maxspeed[i]);
|
||||
}
|
||||
SEND("\n");
|
||||
return ERR_AMOUNT;
|
||||
}
|
||||
|
||||
static errcodes_t cmd_usart_speed(const char* cmd, char* args){
|
||||
int32_t val;
|
||||
if(argsvals(args, NULL, &val)){ // setter
|
||||
if(val < MIN_USART_SPEED || val > MAX_USART_SPEED) return ERR_BADVAL;
|
||||
the_conf.usart_speed = (uint32_t) val;
|
||||
}
|
||||
CMDEQ();
|
||||
SEND(u2str(the_conf.usart_speed));
|
||||
SEND("\n");
|
||||
return ERR_AMOUNT;
|
||||
}
|
||||
|
||||
static errcodes_t servo_valP(const char* cmd, char* args, uint16_t *confval){
|
||||
int32_t val, parno;
|
||||
if(argsvals(args, &parno, &val)){ // setter
|
||||
if(parno < 0 || parno >= SERVO_AMOUNT) return ERR_BADPAR;
|
||||
if(val < SERVO_MINPULSE || val > SERVO_MAXPULSE) return ERR_BADVAL;
|
||||
confval[parno] = (uint16_t) val;
|
||||
}
|
||||
SHOWPARU(cmd, parno, confval[parno]);
|
||||
SEND("\n");
|
||||
return ERR_AMOUNT;
|
||||
}
|
||||
|
||||
static errcodes_t cmd_maxpos(const char* cmd, char* args){
|
||||
return servo_valP(cmd, args, the_conf.maxpulse);
|
||||
}
|
||||
|
||||
static errcodes_t cmd_minpos(const char* cmd, char* args){
|
||||
return servo_valP(cmd, args, the_conf.minpulse);
|
||||
}
|
||||
|
||||
static errcodes_t cmd_startpos(const char* cmd, char* args){
|
||||
return servo_valP(cmd, args, the_conf.startpulse);
|
||||
}
|
||||
|
||||
static errcodes_t cmd_maxspeed(const char* cmd, char* args){
|
||||
int32_t val, parno;
|
||||
if(argsvals(args, &parno, &val)){ // setter
|
||||
if(parno < 0 || parno >= SERVO_AMOUNT) return ERR_BADPAR;
|
||||
if(val < 1 || val > SERVO_MAXSPEED) return ERR_BADVAL;
|
||||
the_conf.maxspeed[parno] = (uint16_t) val;
|
||||
}
|
||||
SHOWPARU(cmd, parno, the_conf.maxspeed[parno]);
|
||||
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;
|
||||
}
|
||||
|
||||
#if 0
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
#endif
|
||||
|
||||
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;
|
||||
}
|
||||
53
G4:G431/Servo/commproto.h
Normal file
53
G4:G431/Servo/commproto.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* This file is part of the servo 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/TODO " 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);
|
||||
186
G4:G431/Servo/flash.c
Normal file
186
G4:G431/Servo/flash.c
Normal file
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* This file is part of the flash project.
|
||||
* 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 <stm32g4.h>
|
||||
#include <string.h> // memcpy
|
||||
|
||||
#include "flash.h"
|
||||
#include "servo.h"
|
||||
|
||||
extern const uint32_t __varsstart, _BLOCKSIZE;
|
||||
|
||||
static const uint32_t blocksize = (uint32_t)&_BLOCKSIZE;
|
||||
|
||||
// max amount of Config records stored (will be recalculate in flashstorage_init()
|
||||
uint32_t maxCnum = 1024 / sizeof(user_conf); // can't use blocksize here
|
||||
|
||||
#define USERCONF_INITIALIZER { \
|
||||
.userconf_sz = sizeof(user_conf) \
|
||||
,.usart_speed = 115200 \
|
||||
,.startpulse = {SG90_MIDPULSE, SG90_MIDPULSE, SG90_MIDPULSE, SG90_MIDPULSE} \
|
||||
,.minpulse = {SG90_MINPULSE, SG90_MINPULSE, SG90_MINPULSE, SG90_MINPULSE} \
|
||||
,.maxpulse = {SG90_MAXPULSE, SG90_MAXPULSE, SG90_MAXPULSE, SG90_MAXPULSE} \
|
||||
,.maxspeed = {SG90_MAXSPEED, SG90_MAXSPEED, SG90_MAXSPEED, SG90_MAXSPEED} \
|
||||
}
|
||||
|
||||
static int erase_flash(const void*, const void*);
|
||||
static int write2flash(const void*, const void*, uint32_t);
|
||||
// don't write `static` here, or get error:
|
||||
// 'memcpy' forming offset 8 is out of the bounds [0, 4] of object '__varsstart' with type 'uint32_t'
|
||||
const user_conf *Flash_Data = (const user_conf *)(&__varsstart);
|
||||
|
||||
user_conf the_conf = USERCONF_INITIALIZER;
|
||||
|
||||
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_flash(Flash_Data, NULL)) 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;
|
||||
}
|
||||
// FLASH_SR_BSY2 for some
|
||||
uint32_t count = (stor_size + 7) / 8;
|
||||
volatile uint32_t *address = (volatile uint32_t*) start;
|
||||
const uint32_t *data = (const uint32_t*) wrdata;
|
||||
for(uint32_t i = 0; i < count; ++i){
|
||||
while (FLASH->SR & (FLASH_SR_BSY)); // 1: check BSY
|
||||
if(FLASH->SR & FLASH_SR_WRPERR){ // 2: check errors
|
||||
return 1; // write protection
|
||||
}
|
||||
FLASH->SR = 0xffff; // clear all flags
|
||||
FLASH->CR |= FLASH_CR_PG; // 3: set PG bit
|
||||
IWDG->KR = IWDG_REFRESH;
|
||||
*address++ = *data++; // 4: write both 32 bit words
|
||||
*address++ = *data++;
|
||||
while(FLASH->SR & FLASH_SR_BSY);
|
||||
if(FLASH->SR & FLASH_SR_PGSERR){
|
||||
ret = 1; // program error - meet not 0xffff
|
||||
break;
|
||||
}/*else{
|
||||
for(int _ = 0; _ < 9999 && (!(FLASH->SR & FLASH_SR_EOP)); ++_);
|
||||
}*/
|
||||
FLASH->SR = 0xffff;
|
||||
}
|
||||
FLASH->CR &= ~(FLASH_CR_PG); // 7: clear PG bit at end of process
|
||||
FLASH->CR |= FLASH_CR_LOCK; // lock it back
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief erase_flash - erase N pages of flash memory
|
||||
* @param start - first address
|
||||
* @param end - last address (or NULL if need to erase all flash remaining)
|
||||
* @return 0 if succeed
|
||||
*/
|
||||
static int erase_flash(const void *start, const void *end){
|
||||
int ret = 0;
|
||||
if(!start) return 1;
|
||||
uint32_t startb = (((uint32_t)start - FLASH_BASE) + blocksize - 1) / blocksize, endb;
|
||||
if(!end){ // erase all remaining
|
||||
endb = FLASH_SIZE * 1024 / blocksize;
|
||||
}else{ // erase a part
|
||||
endb = (((uint32_t)end - FLASH_BASE) + blocksize - 1) / blocksize;
|
||||
}
|
||||
if ((FLASH->CR & FLASH_CR_LOCK) != 0){
|
||||
FLASH->KEYR = FLASH_KEY1;
|
||||
FLASH->KEYR = FLASH_KEY2;
|
||||
}
|
||||
for(uint32_t i = startb; i < endb; ++i){
|
||||
IWDG->KR = IWDG_REFRESH;
|
||||
/* (1) Wait till no operation is on going */
|
||||
/* (2) Clear error & EOP bits */
|
||||
while ((FLASH->SR & FLASH_SR_BSY) != 0){} /* (1) */
|
||||
FLASH->SR = 0xffff; /* (2) */
|
||||
/* (1) Set the PER bit in the FLASH_CR register to enable page erasing */
|
||||
/* (2) Select the page to erase (PNB) */
|
||||
/* (3) Set the STRT bit in the FLASH_CR register to start the erasing */
|
||||
/* (4) Wait until BSY1 cleared */
|
||||
FLASH->CR |= FLASH_CR_PER | i << FLASH_CR_PNB_Pos; /* (1) (2) */
|
||||
FLASH->CR |= FLASH_CR_STRT; /* (3) */
|
||||
while ((FLASH->SR & FLASH_SR_BSY) != 0){} /* (4) */
|
||||
FLASH->SR = FLASH_SR_EOP;
|
||||
if(FLASH->SR & FLASH_SR_WRPERR){ /* Check Write protection error */
|
||||
ret = 1;
|
||||
FLASH->SR = FLASH_SR_WRPERR; /* Clear the flag by software by writing it at 1*/
|
||||
break;
|
||||
}
|
||||
FLASH->CR &= ~FLASH_CR_PER; // clear PER
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int erase_storage(){
|
||||
return erase_flash(Flash_Data, NULL);
|
||||
}
|
||||
52
G4:G431/Servo/flash.h
Normal file
52
G4:G431/Servo/flash.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* This file is part of the flash project.
|
||||
* 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>
|
||||
#include "hardware.h"
|
||||
|
||||
// register with flash size (in blocks)
|
||||
#ifndef FLASH_SIZE_REG
|
||||
#define FLASH_SIZE_REG ((uint32_t)0x1FFF75E0)
|
||||
#endif
|
||||
#ifndef FLASH_SIZE
|
||||
#define FLASH_SIZE *((uint16_t*)FLASH_SIZE_REG)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* struct to save user configurations
|
||||
*/
|
||||
typedef struct __attribute__((packed, aligned(8))){
|
||||
uint16_t userconf_sz; // "magick number"
|
||||
uint32_t usart_speed; // speed of interface
|
||||
uint16_t startpulse[SERVO_AMOUNT]; // starting position
|
||||
uint16_t minpulse[SERVO_AMOUNT]; // minimal position
|
||||
uint16_t maxpulse[SERVO_AMOUNT]; // maximal position
|
||||
uint16_t maxspeed[SERVO_AMOUNT]; // maximal speed
|
||||
} user_conf;
|
||||
|
||||
extern user_conf the_conf; // global user config (read from FLASH to RAM)
|
||||
extern int currentconfidx;
|
||||
extern uint32_t maxCnum;
|
||||
|
||||
void flashstorage_init();
|
||||
int store_userconf();
|
||||
void dump_userconf();
|
||||
int erase_storage();
|
||||
|
||||
96
G4:G431/Servo/hardware.c
Normal file
96
G4:G431/Servo/hardware.c
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* This file is part of the usart 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 <stm32g4.h>
|
||||
|
||||
#include "flash.h"
|
||||
#include "hardware.h"
|
||||
#include "servo.h"
|
||||
|
||||
volatile uint32_t Tms = 0;
|
||||
|
||||
/* Called when systick fires */
|
||||
void sys_tick_handler(){
|
||||
++Tms;
|
||||
}
|
||||
|
||||
// try to set servo; if wrong return false
|
||||
bool set_servo(uint8_t N, uint16_t val){
|
||||
if(N >= SERVO_AMOUNT) return false;
|
||||
if(val < the_conf.minpulse[N] || val > the_conf.maxpulse[N]) return false;
|
||||
volatile uint32_t *CCR = &(TIM3->CCR1);
|
||||
CCR[N] = val;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool get_servo(uint8_t N, uint16_t *val){
|
||||
if(N >= SERVO_AMOUNT) return false;
|
||||
volatile uint32_t *CCR = &(TIM3->CCR1);
|
||||
if(val) *val = CCR[N];
|
||||
return true;
|
||||
}
|
||||
|
||||
static void tim3_setup(){
|
||||
RCC->APB1ENR1 |= RCC_APB1ENR1_TIM3EN;
|
||||
__DSB();
|
||||
// PWM mode 1 (active -> inactive) on all three channels; preload enabled
|
||||
TIM3->CCMR1 = TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1 |
|
||||
TIM_CCMR1_OC2M_2 | TIM_CCMR1_OC2M_1 |
|
||||
TIM_CCMR1_OC1PE | TIM_CCMR1_OC2PE;
|
||||
TIM3->CCMR2 = TIM_CCMR2_OC3M_2 | TIM_CCMR2_OC3M_1 |
|
||||
TIM_CCMR2_OC4M_2 | TIM_CCMR2_OC4M_1 |
|
||||
TIM_CCMR2_OC3PE | TIM_CCMR2_OC4PE;
|
||||
// frequency
|
||||
TIM3->PSC = 84; // 1MHz -> 1us per tick
|
||||
// ARR for PWM
|
||||
TIM3->ARR = 19999; // 20ms, 50Hz
|
||||
// CCRx - minimal position
|
||||
TIM3->CCR1 = the_conf.startpulse[0];
|
||||
TIM3->CCR2 = the_conf.startpulse[1];
|
||||
TIM3->CCR3 = the_conf.startpulse[2];
|
||||
TIM3->CCR4 = the_conf.startpulse[3];
|
||||
// enable main output (don't need for TIM3)
|
||||
//TIMx->BDTR |= TIM_BDTR_MOE;
|
||||
// enable PWM output
|
||||
TIM3->CCER = TIM_CCER_CC1E | TIM_CCER_CC2E |TIM_CCER_CC3E | TIM_CCER_CC4E;
|
||||
// enable timer & ARR buffering
|
||||
TIM3->CR1 |= TIM_CR1_CEN | TIM_CR1_ARPE;
|
||||
// update buffers
|
||||
TIM3->EGR = TIM_EGR_UG;
|
||||
}
|
||||
|
||||
// TIM3 channels: 1 - PA6 (AF2), 2 - PA7 (AF2), 3 - PB0 (AF2), 4 - PB1 (AF2)
|
||||
void gpio_setup(){
|
||||
RCC->AHB2ENR = RCC_AHB2ENR_GPIOAEN | RCC_AHB2ENR_GPIOBEN | RCC_AHB2ENR_GPIOCEN;
|
||||
__DSB(); // Data Synchronization Barrier - wait until data will be really written to AHB2ENR
|
||||
// set PC6 as push-pull output, PC13 is pulldown input, other as default (AIN)
|
||||
GPIOC->MODER = (0xffffffff & ~(GPIO_MODER_MODE6 | GPIO_MODER_MODE13)) | MODER_O(6) | MODER_I(13);
|
||||
GPIOC->PUPDR = PUPD_PD(13); // pulldown
|
||||
GPIOC->OTYPER = OTYPER_PP(6); // push-pull (default)
|
||||
// PA9 (Tx) and PA10 (Rx) (don't forget about SWDIO)
|
||||
GPIOA->MODER = (0xabffffff & ~(GPIO_MODER_MODE6 | GPIO_MODER_MODE7 | GPIO_MODER_MODE9 | GPIO_MODER_MODE10)) |
|
||||
MODER_AF(6) | MODER_AF(7) | MODER_AF(9) | MODER_AF(10);
|
||||
GPIOA->AFR[0] = AFRf(2, 6) | AFRf(2, 7);
|
||||
GPIOA->AFR[1] = AFRf(7, 9) | AFRf(7, 10); // SWDIO is 0 by default; USART1 is AF7
|
||||
GPIOB->MODER = (0xffffffff & ~(GPIO_MODER_MODE0 | GPIO_MODER_MODE1)) |
|
||||
MODER_AF(0) | MODER_AF(1);
|
||||
GPIOB->AFR[0] = AFRf(2, 0) | AFRf(2, 1);
|
||||
// count milliseconds
|
||||
SysTick_Config(SysFreq / 1000); // arg should be < 0xffffff
|
||||
tim3_setup();
|
||||
}
|
||||
42
G4:G431/Servo/hardware.h
Normal file
42
G4:G431/Servo/hardware.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* This file is part of the usart 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
|
||||
|
||||
// KEY (intpullup->0) - PC13
|
||||
// LED - PC6
|
||||
#define KEY_PORT GPIOC
|
||||
#define KEY_PIN (1<<13)
|
||||
#define LED_PORT GPIOC
|
||||
#define LED_PIN (1<<6)
|
||||
#define KEY_PRESSED() (pin_read(KEY_PORT, KEY_PIN) == 1)
|
||||
#define LED_ON() do{pin_set(LED_PORT, LED_PIN);}while(0)
|
||||
#define LED_OFF() do{pin_clear(LED_PORT, LED_PIN);}while(0)
|
||||
#define LED_TOGG() do{pin_toggle(LED_PORT, LED_PIN);}while(0)
|
||||
|
||||
#define SERVO_AMOUNT 4
|
||||
|
||||
#define MIN_USART_SPEED 1200
|
||||
#define MAX_USART_SPEED 3000000
|
||||
|
||||
extern volatile uint32_t Tms;
|
||||
|
||||
void gpio_setup();
|
||||
|
||||
bool set_servo(uint8_t N, uint16_t val);
|
||||
bool get_servo(uint8_t N, uint16_t *val);
|
||||
66
G4:G431/Servo/main.c
Normal file
66
G4:G431/Servo/main.c
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* This file is part of the blink project.
|
||||
* 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 <stm32g4.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "commproto.h"
|
||||
#include "flash.h"
|
||||
#include "hardware.h"
|
||||
#include "servo.h"
|
||||
#include "usart.h"
|
||||
|
||||
// 40ms for debouncing
|
||||
#define TDEBOUNCE (40)
|
||||
|
||||
int main(void){
|
||||
StartHSE();
|
||||
// init storage first to take base values
|
||||
flashstorage_init();
|
||||
gpio_setup();
|
||||
usart_setup(the_conf.usart_speed);
|
||||
bool pressed = false;
|
||||
uint32_t Tblink = 0, Tkey = 0; // blink and key pressed time
|
||||
while(1){
|
||||
USART_flags_t f = usart_process();
|
||||
if(f.rxovrfl) usart_sendstr("Rx buffer overflow!\n");
|
||||
if(f.txerr) usart_sendstr("Tx error!\n");
|
||||
char *str = usart_getline();
|
||||
if(str){
|
||||
const char *ans = parse_cmd(usart_sendstr, str);
|
||||
if(ans) usart_sendstr(ans);
|
||||
}
|
||||
process_servo();
|
||||
if(Tms - Tblink > 499){
|
||||
LED_TOGG();
|
||||
Tblink = Tms;
|
||||
}
|
||||
if(KEY_PRESSED()){
|
||||
if(!pressed){ // new event
|
||||
pressed = true;
|
||||
usart_sendstr("Key pressed\n");
|
||||
}
|
||||
Tkey = Tms;
|
||||
}else{ // key released
|
||||
if(pressed && (Tms - Tkey) > TDEBOUNCE){ // wait for debounce
|
||||
pressed = false;
|
||||
usart_sendstr("Key released\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
4
G4:G431/Servo/openocd.cfg
Normal file
4
G4:G431/Servo/openocd.cfg
Normal file
@@ -0,0 +1,4 @@
|
||||
set FLASH_SIZE 0x20000
|
||||
|
||||
source [find interface/stlink-v2-1.cfg]
|
||||
source [find target/stm32g4x.cfg]
|
||||
203
G4:G431/Servo/ringbuffer.c
Normal file
203
G4:G431/Servo/ringbuffer.c
Normal 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 <stm32g4.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 = true;
|
||||
int l = datalen(b);
|
||||
b->busy = false;
|
||||
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 = true;
|
||||
int ret = hasbyte(b, byte);
|
||||
b->busy = false;
|
||||
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 = true;
|
||||
int r = read(b, s, len);
|
||||
b->busy = false;
|
||||
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 = true;
|
||||
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 = false;
|
||||
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 = true;
|
||||
int n = lento(b, byte);
|
||||
b->busy = false;
|
||||
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 = true;
|
||||
int w = write(b, str, l);
|
||||
b->busy = false;
|
||||
return w;
|
||||
}
|
||||
|
||||
// just delete all information in buffer `b`
|
||||
int RB_clearbuf(ringbuffer *b){
|
||||
CHK(b);
|
||||
if(b->busy) return -1;
|
||||
b->busy = true;
|
||||
b->head = 0;
|
||||
b->tail = 0;
|
||||
memset(b->data, 0, b->length);
|
||||
b->busy = false;
|
||||
return 1;
|
||||
}
|
||||
36
G4:G431/Servo/ringbuffer.h
Normal file
36
G4:G431/Servo/ringbuffer.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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>
|
||||
|
||||
typedef struct{
|
||||
uint8_t *data; // data buffer
|
||||
const int length; // its length
|
||||
int head; // head index
|
||||
int tail; // tail index
|
||||
volatile bool 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);
|
||||
BIN
G4:G431/Servo/servo.bin
Executable file
BIN
G4:G431/Servo/servo.bin
Executable file
Binary file not shown.
84
G4:G431/Servo/servo.c
Normal file
84
G4:G431/Servo/servo.c
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* This file is part of the servo 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 <stm32g4.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "flash.h"
|
||||
#include "hardware.h"
|
||||
#include "servo.h"
|
||||
|
||||
static uint16_t servo_tagpos[SERVO_AMOUNT] = {SG90_MIDPULSE, SG90_MIDPULSE, SG90_MIDPULSE, SG90_MIDPULSE};
|
||||
static uint16_t servo_speed[SERVO_AMOUNT] = {0, 0, 0, 0};
|
||||
|
||||
void process_servo(){
|
||||
static bool notinited = true;
|
||||
#if SERVO_AMOUNT != 4
|
||||
#error "Add timers' variables"
|
||||
#endif
|
||||
static const uint32_t trgflags[SERVO_AMOUNT] = {TIM_SR_CC1IF, TIM_SR_CC2IF, TIM_SR_CC3IF, TIM_SR_CC4IF};
|
||||
if(notinited){ // init tagpos with starting values from settings at first run
|
||||
for(int i = 0; i < SERVO_AMOUNT; ++i) servo_tagpos[i] = the_conf.startpulse[i];
|
||||
notinited = false;
|
||||
}
|
||||
uint32_t sr = TIM3->SR;
|
||||
uint32_t clear_mask = 0;
|
||||
for(uint8_t i = 0; i < SERVO_AMOUNT; ++i){
|
||||
uint16_t curpos;
|
||||
if(!get_servo(i, &curpos) || !(sr & trgflags[i])) continue;
|
||||
clear_mask |= trgflags[i];
|
||||
if(servo_tagpos[i] == curpos || servo_speed[i] < 1) continue;
|
||||
int32_t newpos = curpos;
|
||||
if(servo_tagpos[i] > curpos){
|
||||
newpos += servo_speed[i];
|
||||
if(newpos > servo_tagpos[i]) newpos = servo_tagpos[i];
|
||||
}else{
|
||||
newpos -= servo_speed[i];
|
||||
if(newpos < servo_tagpos[i]) newpos = servo_tagpos[i];
|
||||
}
|
||||
set_servo(i, (uint16_t) newpos);
|
||||
}
|
||||
if(clear_mask) TIM3->SR &= ~clear_mask;
|
||||
}
|
||||
|
||||
// set speed in steps per tick
|
||||
bool servo_set_speed(uint8_t N, uint16_t s){
|
||||
if(N >= SERVO_AMOUNT) return false;
|
||||
if(s > the_conf.maxspeed[N]) return false; // let s==0: this will allow to stop mowing to tagpos
|
||||
servo_speed[N] = s;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool servo_get_speed(uint8_t N, uint16_t *s){
|
||||
if(N >= SERVO_AMOUNT) return false;
|
||||
if(s) *s = servo_speed[N];
|
||||
return true;
|
||||
}
|
||||
|
||||
bool servo_set_tagpos(uint8_t N, uint16_t pos){
|
||||
if(N >= SERVO_AMOUNT) return false;
|
||||
if(pos < the_conf.minpulse[N] || pos > the_conf.maxpulse[N]) return false;
|
||||
servo_tagpos[N] = pos;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool servo_get_tagpos(uint8_t N, uint16_t *pos){
|
||||
if(N >= SERVO_AMOUNT) return false;
|
||||
if(pos) *pos = servo_tagpos[N];
|
||||
return true;
|
||||
}
|
||||
1
G4:G431/Servo/servo.cflags
Normal file
1
G4:G431/Servo/servo.cflags
Normal file
@@ -0,0 +1 @@
|
||||
-std=c23
|
||||
3
G4:G431/Servo/servo.config
Normal file
3
G4:G431/Servo/servo.config
Normal file
@@ -0,0 +1,3 @@
|
||||
#define EBUG
|
||||
#define STM32G4
|
||||
#define STM32G431xx
|
||||
1
G4:G431/Servo/servo.creator
Normal file
1
G4:G431/Servo/servo.creator
Normal file
@@ -0,0 +1 @@
|
||||
[General]
|
||||
1
G4:G431/Servo/servo.cxxflags
Normal file
1
G4:G431/Servo/servo.cxxflags
Normal file
@@ -0,0 +1 @@
|
||||
-std=c++23
|
||||
15
G4:G431/Servo/servo.files
Normal file
15
G4:G431/Servo/servo.files
Normal file
@@ -0,0 +1,15 @@
|
||||
commproto.cpp
|
||||
commproto.h
|
||||
flash.c
|
||||
flash.h
|
||||
hardware.c
|
||||
hardware.h
|
||||
main.c
|
||||
ringbuffer.c
|
||||
ringbuffer.h
|
||||
servo.c
|
||||
servo.h
|
||||
strfunc.c
|
||||
strfunc.h
|
||||
usart.c
|
||||
usart.h
|
||||
39
G4:G431/Servo/servo.h
Normal file
39
G4:G431/Servo/servo.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* This file is part of the servo 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
|
||||
|
||||
// Default starting values
|
||||
// minimal and maximal pulse length for SG90
|
||||
#define SG90_MINPULSE 400
|
||||
#define SG90_MAXPULSE 2600
|
||||
#define SG90_MIDPULSE ((SG90_MINPULSE+SG90_MAXPULSE)/2)
|
||||
#define SG90_AMPL (SG90_MAXPULSE-SG90_MINPULSE)
|
||||
// maximal speed: 0.1s (5 ticks) per 60degr (1/3 of range): (SG90_AMPL/15)
|
||||
#define SG90_MAXSPEED 93
|
||||
|
||||
// Limiting values
|
||||
#define SERVO_MINPULSE 100
|
||||
#define SERVO_MAXPULSE 5000
|
||||
#define SERVO_MAXSPEED 300
|
||||
|
||||
void process_servo();
|
||||
bool servo_set_speed(uint8_t N, uint16_t s);
|
||||
bool servo_get_speed(uint8_t N, uint16_t *s);
|
||||
bool servo_set_tagpos(uint8_t N, uint16_t pos);
|
||||
bool servo_get_tagpos(uint8_t N, uint16_t *pos);
|
||||
4
G4:G431/Servo/servo.includes
Normal file
4
G4:G431/Servo/servo.includes
Normal file
@@ -0,0 +1,4 @@
|
||||
.
|
||||
../inc
|
||||
../inc/Fx
|
||||
../inc/cm
|
||||
265
G4:G431/Servo/strfunc.c
Normal file
265
G4:G431/Servo/strfunc.c
Normal file
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
* 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 <stm32g4.h>
|
||||
#include <string.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;
|
||||
}
|
||||
31
G4:G431/Servo/strfunc.h
Normal file
31
G4:G431/Servo/strfunc.h
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
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);
|
||||
|
||||
257
G4:G431/Servo/usart.c
Normal file
257
G4:G431/Servo/usart.c
Normal file
@@ -0,0 +1,257 @@
|
||||
/*
|
||||
* 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 <stm32g4.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "hardware.h" // Tms
|
||||
#include "ringbuffer.h"
|
||||
#include "usart.h"
|
||||
|
||||
// USART for text-based protocol, each data portion ends with '\n'
|
||||
// RX works over circular DMA
|
||||
|
||||
// USART-depending part ------->
|
||||
// USART1 @ PA9 (Tx) - MUX25 - and PA10 (Rx) - MUX24
|
||||
// select USART and its DMA channels
|
||||
#define USARTx USART1
|
||||
#define USARTxAPB APB2ENR
|
||||
#define USARTxEN RCC_APB2ENR_USART1EN
|
||||
#define USART_APBEN RCC_APB2
|
||||
// DMAMUX channels: 24 - USART1Rx, 25 - USART1Tx
|
||||
#define DMAMUXRXN (24)
|
||||
#define DMAMUXTXN (25)
|
||||
// DMA channels: 1 (0 in MUX) - Rx, 2 (1 in MUX) - Tx; TC and error flags
|
||||
// use DMA ch2/3 because they both have single IRQ
|
||||
#define DMAx DMA1
|
||||
#define DMAxEN (RCC_AHB1ENR_DMA1EN | RCC_AHB1ENR_DMAMUX1EN)
|
||||
#define DMACHRX DMA1_Channel1
|
||||
#define DMARXTCF DMA_ISR_TCIF1
|
||||
#define DMARXEF DMA_ISR_TEIF1
|
||||
#define DMACHTX DMA1_Channel2
|
||||
#define DMATXTCF DMA_ISR_TCIF2
|
||||
#define DMATXEF DMA_ISR_TEIF2
|
||||
#define DMAMUXRX DMAMUX1_Channel0
|
||||
#define DMAMUXTX DMAMUX1_Channel1
|
||||
#define USARTIRQn USART1_IRQn
|
||||
#define DMARXIRQ DMA1_Channel1_IRQn
|
||||
#define DMATXIRQ DMA1_Channel2_IRQn
|
||||
|
||||
// interrupt aliases
|
||||
static void usart_isr();
|
||||
static void dmatx_isr();
|
||||
static void dmarx_isr();
|
||||
void usart1_isr() __attribute__ ((alias ("usart_isr")));
|
||||
void dma1_channel1_isr() __attribute__ ((alias ("dmarx_isr")));
|
||||
void dma1_channel2_isr() __attribute__ ((alias ("dmatx_isr")));
|
||||
// <-------- USART-depending part
|
||||
|
||||
// RX/TX DMA->CCR without EN flag
|
||||
#define DMARXCCR (DMA_CCR_MINC | DMA_CCR_CIRC | DMA_CCR_TEIE)
|
||||
#define DMATXCCR (DMA_CCR_MINC | DMA_CCR_DIR | DMA_CCR_TCIE | DMA_CCR_TEIE)
|
||||
|
||||
static volatile bool gotstring = true; // got '\n' in input stream -> force data reading to inbuf
|
||||
static volatile bool txrdy = true; // Tx DMA not busy
|
||||
static volatile USART_flags_t curflags; // current flags (cleared in `usart_process`)
|
||||
// rx/tx DMA buffers
|
||||
static uint8_t dmarxbuf[USARTRXDMABUFSZ];
|
||||
static uint8_t dmatxbuf[USARTTXDMABUFSZ];
|
||||
// index of last DMA read position
|
||||
static uint32_t dma_read_idx = 0;
|
||||
// for ringbuffer
|
||||
static uint8_t rbrxbuf[USARTRXBUFSZ], rbtxbuf[USARTTXBUFSZ];
|
||||
static ringbuffer TxRB = {.data = rbtxbuf, .length = USARTTXBUFSZ};
|
||||
static ringbuffer RxRB = {.data = rbrxbuf, .length = USARTRXBUFSZ};
|
||||
|
||||
#define USART_BRR(speed) ((SysFreq + (speed)/2) / (speed))
|
||||
|
||||
static void reinit_rx_dma(){
|
||||
dma_read_idx = 0;
|
||||
RB_clearbuf(&RxRB);
|
||||
DMACHRX->CCR = DMARXCCR; // stop to reload
|
||||
DMACHRX->CNDTR = USARTRXDMABUFSZ;
|
||||
DMACHRX->CMAR = (uint32_t) dmarxbuf;
|
||||
DMACHRX->CCR = DMARXCCR | DMA_CCR_EN;
|
||||
}
|
||||
|
||||
void usart_setup(uint32_t speed){
|
||||
RCC->AHB1ENR |= DMAxEN; // enable DMA
|
||||
// enable USART clocking
|
||||
RCC->USARTxAPB |= USARTxEN;
|
||||
// baudrate
|
||||
USARTx->BRR = USART_BRR(speed);
|
||||
// eol character: '/n'
|
||||
USARTx->CR2 = USART_CR2_ADD_VAL('\n');
|
||||
// enable DMA transmission
|
||||
USARTx->CR3 = USART_CR3_DMAT | USART_CR3_DMAR;
|
||||
// set up DMA channels
|
||||
// Tx channel: mem++, mem->periph, 8bit, compl.&err. irq
|
||||
DMACHTX->CCR = DMATXCCR;
|
||||
DMACHTX->CPAR = (uint32_t) &USARTx->TDR; // peripherial address
|
||||
// Rx channel: mem++, periph->mem, 8bit, compl.&err. irq
|
||||
DMACHRX->CCR = DMARXCCR;
|
||||
DMACHRX->CPAR = (uint32_t) &USARTx->RDR; // peripherial address
|
||||
// set up DMAMUX channels
|
||||
// enumeration of DMAMUX starts from 0 (DMA - from 1)!
|
||||
DMAMUXRX->CCR = DMAMUXRXN;
|
||||
DMAMUXTX->CCR = DMAMUXTXN;
|
||||
// charmatch interrupt, enable transmitter and receiver, enable usart
|
||||
USARTx->CR1 = USART_CR1_CMIE | USART_CR1_TE | USART_CR1_RE | USART_CR1_UE;
|
||||
USARTx->ICR = 0xffffffff; // clear all flags
|
||||
reinit_rx_dma();
|
||||
NVIC_EnableIRQ(USARTIRQn);
|
||||
NVIC_EnableIRQ(DMARXIRQ);
|
||||
NVIC_EnableIRQ(DMATXIRQ);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief usart_sendbuf - send next data portion
|
||||
* @return true if sent something
|
||||
*/
|
||||
static bool usart_sendbuf(){
|
||||
if(!txrdy) return false;
|
||||
int rd = RB_read(&TxRB, dmatxbuf, USARTTXDMABUFSZ);
|
||||
if(rd < 1) return false; // nothing to write or busy
|
||||
// set up DMA
|
||||
DMACHTX->CCR = DMATXCCR;
|
||||
DMACHTX->CMAR = (uint32_t) dmatxbuf;
|
||||
DMACHTX->CNDTR = rd;
|
||||
USARTx->ICR = USART_ICR_TCCF; // clear TC flag
|
||||
txrdy = false;
|
||||
// activate DMA
|
||||
DMACHTX->CCR = DMATXCCR | DMA_CCR_EN;
|
||||
return true;
|
||||
}
|
||||
|
||||
int usart_send(const char *str, int len){
|
||||
if(!str || len < 1) return 0;
|
||||
uint32_t t = Tms;
|
||||
int sent = 0;
|
||||
do{
|
||||
IWDG->KR = IWDG_REFRESH;
|
||||
int put = RB_write(&TxRB, (uint8_t*)str, len);
|
||||
if(put < 0) continue; // busy
|
||||
else if(put == 0){
|
||||
usart_sendbuf(); // no place
|
||||
t = Tms;
|
||||
}else{
|
||||
len -= put;
|
||||
sent += put;
|
||||
str += put;
|
||||
}
|
||||
}while(len && (Tms - t) < USARTBLKTMOUT); // not more than `block` ms!
|
||||
return sent;
|
||||
}
|
||||
|
||||
int usart_sendstr(const char *str){
|
||||
int l = strlen(str);
|
||||
return usart_send(str, l);
|
||||
}
|
||||
|
||||
static void addtoreadidx(int adder){
|
||||
dma_read_idx += adder;
|
||||
if(dma_read_idx >= USARTRXDMABUFSZ) dma_read_idx -= USARTRXDMABUFSZ;
|
||||
}
|
||||
|
||||
// return current flags
|
||||
USART_flags_t usart_process(){
|
||||
static uint32_t Tlast = 0;
|
||||
USART_flags_t flags = curflags;
|
||||
curflags.all = 0;
|
||||
if(RB_datalento(&TxRB, '\n') > 1 || Tms - Tlast >= USARTSENDTMOUT){ // send buffer as we found '\n' or each 10ms
|
||||
if(usart_sendbuf()) Tlast = Tms;
|
||||
}
|
||||
int remained = DMACHRX->CNDTR;
|
||||
int write_idx = USARTRXDMABUFSZ - remained; // next symbol to be written
|
||||
int available = (write_idx - dma_read_idx); // length of data available
|
||||
if(available < 0) available += USARTRXDMABUFSZ; // write to the left of read
|
||||
if(available == 0) return flags;
|
||||
// add next data portion to RX ring buffer
|
||||
if(available >= (USARTRXDMABUFSZ / 2) || gotstring){
|
||||
// copy data in one or two chunks (wrap handling)
|
||||
// check if we can write to RB `available` bytes
|
||||
int rballow = RxRB.length - 1 - RB_datalen(&RxRB);
|
||||
if(rballow < available){
|
||||
if(available > USARTRXDMABUFSZ - 2){ // near overfull
|
||||
flags.rxovrfl = 1;
|
||||
reinit_rx_dma();
|
||||
RB_clearbuf(&RxRB);
|
||||
return flags;
|
||||
}
|
||||
if(rballow < 1) return flags;
|
||||
available = rballow; // read at least as we can
|
||||
}
|
||||
if(dma_read_idx + available <= USARTRXDMABUFSZ){ // head before tail
|
||||
int written = RB_write(&RxRB, &dmarxbuf[dma_read_idx], available);
|
||||
if(written == available && dmarxbuf[dma_read_idx+available-1] == '\n') gotstring = 0;
|
||||
if(written > 0) addtoreadidx(written);
|
||||
}else{ // head after tail - two chunks
|
||||
int first = USARTRXDMABUFSZ - dma_read_idx;
|
||||
int written = RB_write(&RxRB, &dmarxbuf[dma_read_idx], first);
|
||||
if(written != first){ // could write only part - just increase read index
|
||||
if(written > 0) addtoreadidx(written);
|
||||
}else{
|
||||
dma_read_idx = 0;
|
||||
int last = available - first;
|
||||
written = RB_write(&RxRB, dmarxbuf, last);
|
||||
if(written == last && dmarxbuf[last-1] == '\n') gotstring = 0;
|
||||
if(written > 0) addtoreadidx(written);
|
||||
}
|
||||
}
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
char *usart_getline(){
|
||||
static char buff[MAX_INPLEN];
|
||||
int l = RB_datalento(&RxRB, '\n');
|
||||
if(l < 1){
|
||||
l = RB_datalen(&RxRB); // Rx ringbuffer could be near overflow but without '\n'
|
||||
if(l < MAX_INPLEN-1) return NULL; // allow to wait for last symbols
|
||||
}
|
||||
if(l > MAX_INPLEN-1){ // overflow -> read at least part of the string
|
||||
l = MAX_INPLEN-1;
|
||||
}
|
||||
if(l != RB_read(&RxRB, (uint8_t*)buff, l)) return NULL;
|
||||
buff[l] = 0; // return with '\n' at end of line (so user can detect non-finished overflowed lines)
|
||||
return buff;
|
||||
}
|
||||
|
||||
// interrupt by '\n'
|
||||
static void usart_isr(){
|
||||
if(USARTx->ISR & USART_ISR_CMF){ // got '\n' @ USARTx
|
||||
gotstring = true;
|
||||
}
|
||||
USARTx->ICR = 0xffffffff; // clear all flags
|
||||
}
|
||||
|
||||
static void dmarx_isr(){
|
||||
volatile uint32_t isr = DMAx->ISR;
|
||||
if(isr & DMARXEF){ // error
|
||||
reinit_rx_dma();
|
||||
curflags.rxovrfl = 1;
|
||||
}
|
||||
DMAx->IFCR = DMARXEF; // clear all flags
|
||||
}
|
||||
|
||||
static void dmatx_isr(){
|
||||
volatile uint32_t isr = DMAx->ISR;
|
||||
if(isr & DMATXTCF) txrdy = true;
|
||||
if(isr & DMATXEF) curflags.txerr = 1;
|
||||
DMAx->IFCR = DMATXTCF | DMATXEF; // clear all flags
|
||||
}
|
||||
49
G4:G431/Servo/usart.h
Normal file
49
G4:G431/Servo/usart.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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>
|
||||
|
||||
// maximal length of input string (including '\n' and terminating zero)
|
||||
#define MAX_INPLEN (128)
|
||||
// Rx/Tx ring buffer size
|
||||
#define USARTTXBUFSZ (512)
|
||||
#define USARTRXBUFSZ (512)
|
||||
// Rx/Tx DMA buffer size
|
||||
#define USARTTXDMABUFSZ (256)
|
||||
#define USARTRXDMABUFSZ (256)
|
||||
|
||||
// blocking timeout - not more than 5ms
|
||||
#define USARTBLKTMOUT (5)
|
||||
// send buffer each 10ms
|
||||
#define USARTSENDTMOUT (10)
|
||||
|
||||
typedef union{
|
||||
struct{
|
||||
uint8_t txerr : 1; // transmit error
|
||||
uint8_t rxovrfl : 1; // receive buffer overflow
|
||||
};
|
||||
uint8_t all;
|
||||
} USART_flags_t;
|
||||
|
||||
void usart_setup(uint32_t speed);
|
||||
int usart_send(const char *str, int len);
|
||||
char *usart_getline();
|
||||
int usart_sendstr(const char *str);
|
||||
USART_flags_t usart_process();
|
||||
2
G4:G431/Servo/version.inc
Normal file
2
G4:G431/Servo/version.inc
Normal file
@@ -0,0 +1,2 @@
|
||||
#define BUILD_NUMBER "28"
|
||||
#define BUILD_DATE "2026-07-29"
|
||||
11
G4:G431/USB_CDC/Makefile
Normal file
11
G4:G431/USB_CDC/Makefile
Normal file
@@ -0,0 +1,11 @@
|
||||
BINARY := usbcdc
|
||||
PREFIX := /usr/bin/arm-none-eabi
|
||||
CFLAGS = -std=c23
|
||||
# MCU code
|
||||
MCU := G431xx
|
||||
# change this linking script depending on particular MCU model,
|
||||
LDSCRIPT := stm32g431xb.ld
|
||||
|
||||
include ../makefile.g4
|
||||
include ../../makefile.stm32
|
||||
|
||||
37
G4:G431/USB_CDC/dbg.h
Normal file
37
G4:G431/USB_CDC/dbg.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
// debugging messages on config interface
|
||||
#ifdef EBUG
|
||||
#include "usart.h"
|
||||
|
||||
#define STR_HELPER(s) #s
|
||||
#define STR(s) STR_HELPER(s)
|
||||
#define DBGl(str) do{usart_sendstr(__FILE__ " (L" STR(__LINE__) "): " str); usart_send("\n", 1);}while(0)
|
||||
#define DBGs(str) usart_sendstr(str)
|
||||
#define DBGch(ch) usart_send(ch, 1)
|
||||
#define DBGn() usart_send("\n", 1)
|
||||
#else
|
||||
#define DBGl(str)
|
||||
#define DBGs(str)
|
||||
#define DBGch(ch)
|
||||
#define DBGn()
|
||||
#define DBGpri()
|
||||
#endif
|
||||
43
G4:G431/USB_CDC/hardware.c
Normal file
43
G4:G431/USB_CDC/hardware.c
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* This file is part of the usart 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 <stm32g4.h>
|
||||
|
||||
#include "hardware.h"
|
||||
|
||||
volatile uint32_t Tms = 0;
|
||||
|
||||
/* Called when systick fires */
|
||||
void sys_tick_handler(){
|
||||
++Tms;
|
||||
}
|
||||
|
||||
void gpio_setup(){
|
||||
RCC->AHB2ENR = RCC_AHB2ENR_GPIOAEN | RCC_AHB2ENR_GPIOCEN; // enable PC & PA (USART1)
|
||||
__DSB(); // Data Synchronization Barrier - wait until data will be really written to AHB2ENR
|
||||
// set PC6 as push-pull output, PC13 is pulldown input, other as default (AIN)
|
||||
GPIOC->MODER = (0xffffffff & ~(GPIO_MODER_MODE6 | GPIO_MODER_MODE13)) | MODER_O(6) | MODER_I(13);
|
||||
GPIOC->PUPDR = PUPD_PD(13); // pulldown
|
||||
GPIOC->OTYPER = OTYPER_PP(6); // push-pull (default)
|
||||
// PA9 (Tx) and PA10 (Rx) (don't forget about SWDIO)
|
||||
GPIOA->MODER = (0xabffffff & ~(GPIO_MODER_MODE9 | GPIO_MODER_MODE10)) | MODER_AF(9) | MODER_AF(10);
|
||||
GPIOA->AFR[1] = AFRf(7, 9) | AFRf(7, 10); // SWDIO is 0 by default; USART1 is AF7
|
||||
// count milliseconds
|
||||
SysTick_Config(SysFreq / 1000); // arg should be < 0xffffff
|
||||
}
|
||||
|
||||
35
G4:G431/USB_CDC/hardware.h
Normal file
35
G4:G431/USB_CDC/hardware.h
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* This file is part of the usart 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
|
||||
|
||||
// KEY (intpullup->0) - PC13
|
||||
// LED - PC6
|
||||
#define KEY_PORT GPIOC
|
||||
#define KEY_PIN (1<<13)
|
||||
#define LED_PORT GPIOC
|
||||
#define LED_PIN (1<<6)
|
||||
#define KEY_PRESSED() (pin_read(KEY_PORT, KEY_PIN) == 1)
|
||||
#define LED_ON() do{pin_set(LED_PORT, LED_PIN);}while(0)
|
||||
#define LED_OFF() do{pin_clear(LED_PORT, LED_PIN);}while(0)
|
||||
#define LED_TOGG() do{pin_toggle(LED_PORT, LED_PIN);}while(0)
|
||||
|
||||
|
||||
extern volatile uint32_t Tms;
|
||||
|
||||
void gpio_setup();
|
||||
83
G4:G431/USB_CDC/main.c
Normal file
83
G4:G431/USB_CDC/main.c
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* This file is part of the blink project.
|
||||
* 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 <stm32g4.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "hardware.h"
|
||||
#include "usart.h"
|
||||
#include "usb_dev.h"
|
||||
|
||||
// 40ms for debouncing
|
||||
#define TDEBOUNCE (40)
|
||||
// USB input buffer
|
||||
#define INBUFSZ (256)
|
||||
|
||||
int main(void){
|
||||
char inbuff[INBUFSZ];
|
||||
const char *spressed = "Key pressed\n", *sreleased = "Key released\n";
|
||||
StartHSE();
|
||||
gpio_setup();
|
||||
usart_setup(115200);
|
||||
USB_setup();
|
||||
uint8_t pressed = 0;
|
||||
uint32_t Tblink = 0, Tkey = 0; // blink and key pressed time
|
||||
while(1){
|
||||
USART_flags_t f = usart_process();
|
||||
if(f.rxovrfl) usart_sendstr("Rx buffer overflow!\n");
|
||||
if(f.txerr) usart_sendstr("Tx error!\n");
|
||||
char *str = usart_getline();
|
||||
if(str){
|
||||
usart_sendstr("Received:\n");
|
||||
usart_sendstr(str);
|
||||
int l = strlen(str);
|
||||
if(str[l-1] != '\n'){
|
||||
usart_sendstr("\n(only part of line)\n");
|
||||
}
|
||||
}
|
||||
if(Tms - Tblink > 499){
|
||||
LED_TOGG();
|
||||
Tblink = Tms;
|
||||
}
|
||||
if(KEY_PRESSED()){
|
||||
if(!pressed){ // new event
|
||||
pressed = 1;
|
||||
usart_sendstr(spressed);
|
||||
USB_putbyte('1'); USB_putbyte('1'); newline();
|
||||
USB_sendstr(spressed);
|
||||
}
|
||||
Tkey = Tms;
|
||||
}else{ // key released
|
||||
if(pressed && (Tms - Tkey) > TDEBOUNCE){ // wait for debounce
|
||||
pressed = 0;
|
||||
usart_sendstr(sreleased);
|
||||
USB_putbyte('0'); USB_putbyte('0'); newline();
|
||||
USB_sendstr(sreleased);
|
||||
}
|
||||
}
|
||||
int l = USB_receivestr(inbuff, INBUFSZ);
|
||||
if(l < 0) usart_sendstr("ERROR: USB buffer overflow or string was too long\n");
|
||||
else if(l){
|
||||
usart_sendstr("USB:\n");
|
||||
usart_sendstr(inbuff);
|
||||
usart_send("\n", 1);
|
||||
USB_sendstr(inbuff);
|
||||
newline();
|
||||
}
|
||||
}
|
||||
}
|
||||
4
G4:G431/USB_CDC/openocd.cfg
Normal file
4
G4:G431/USB_CDC/openocd.cfg
Normal file
@@ -0,0 +1,4 @@
|
||||
set FLASH_SIZE 0x20000
|
||||
|
||||
source [find interface/stlink-v2-1.cfg]
|
||||
source [find target/stm32g4x.cfg]
|
||||
203
G4:G431/USB_CDC/ringbuffer.c
Normal file
203
G4:G431/USB_CDC/ringbuffer.c
Normal 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 <stm32g4.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;
|
||||
memset(b->data, 0, b->length);
|
||||
b->busy = 0;
|
||||
return 1;
|
||||
}
|
||||
36
G4:G431/USB_CDC/ringbuffer.h
Normal file
36
G4:G431/USB_CDC/ringbuffer.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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>
|
||||
|
||||
typedef struct{
|
||||
uint8_t *data; // data buffer
|
||||
const int length; // its length
|
||||
volatile int head; // head index
|
||||
volatile int tail; // tail index
|
||||
volatile uint8_t 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);
|
||||
257
G4:G431/USB_CDC/usart.c
Normal file
257
G4:G431/USB_CDC/usart.c
Normal file
@@ -0,0 +1,257 @@
|
||||
/*
|
||||
* 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 <stm32g4.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "hardware.h" // Tms
|
||||
#include "ringbuffer.h"
|
||||
#include "usart.h"
|
||||
|
||||
// USART for text-based protocol, each data portion ends with '\n'
|
||||
// RX works over circular DMA
|
||||
|
||||
// USART-depending part ------->
|
||||
// USART1 @ PA9 (Tx) - MUX25 - and PA10 (Rx) - MUX24
|
||||
// select USART and its DMA channels
|
||||
#define USARTx USART1
|
||||
#define USARTxAPB APB2ENR
|
||||
#define USARTxEN RCC_APB2ENR_USART1EN
|
||||
#define USART_APBEN RCC_APB2
|
||||
// DMAMUX channels: 24 - USART1Rx, 25 - USART1Tx
|
||||
#define DMAMUXRXN (24)
|
||||
#define DMAMUXTXN (25)
|
||||
// DMA channels: 1 (0 in MUX) - Rx, 2 (1 in MUX) - Tx; TC and error flags
|
||||
// use DMA ch2/3 because they both have single IRQ
|
||||
#define DMAx DMA1
|
||||
#define DMAxEN (RCC_AHB1ENR_DMA1EN | RCC_AHB1ENR_DMAMUX1EN)
|
||||
#define DMACHRX DMA1_Channel1
|
||||
#define DMARXTCF DMA_ISR_TCIF1
|
||||
#define DMARXEF DMA_ISR_TEIF1
|
||||
#define DMACHTX DMA1_Channel2
|
||||
#define DMATXTCF DMA_ISR_TCIF2
|
||||
#define DMATXEF DMA_ISR_TEIF2
|
||||
#define DMAMUXRX DMAMUX1_Channel0
|
||||
#define DMAMUXTX DMAMUX1_Channel1
|
||||
#define USARTIRQn USART1_IRQn
|
||||
#define DMARXIRQ DMA1_Channel1_IRQn
|
||||
#define DMATXIRQ DMA1_Channel2_IRQn
|
||||
|
||||
// interrupt aliases
|
||||
static void usart_isr();
|
||||
static void dmatx_isr();
|
||||
static void dmarx_isr();
|
||||
void usart1_isr() __attribute__ ((alias ("usart_isr")));
|
||||
void dma1_channel1_isr() __attribute__ ((alias ("dmarx_isr")));
|
||||
void dma1_channel2_isr() __attribute__ ((alias ("dmatx_isr")));
|
||||
// <-------- USART-depending part
|
||||
|
||||
// RX/TX DMA->CCR without EN flag
|
||||
#define DMARXCCR (DMA_CCR_MINC | DMA_CCR_CIRC | DMA_CCR_TEIE)
|
||||
#define DMATXCCR (DMA_CCR_MINC | DMA_CCR_DIR | DMA_CCR_TCIE | DMA_CCR_TEIE)
|
||||
|
||||
static volatile uint8_t gotstring = 1; // got '\n' in input stream -> force data reading to inbuf
|
||||
static volatile uint8_t txrdy = 1; // Tx DMA not busy
|
||||
static volatile USART_flags_t curflags; // current flags (cleared in `usart_process`)
|
||||
// rx/tx DMA buffers
|
||||
static uint8_t dmarxbuf[USARTRXDMABUFSZ];
|
||||
static uint8_t dmatxbuf[USARTTXDMABUFSZ];
|
||||
// index of last DMA read position
|
||||
static volatile uint32_t dma_read_idx = 0;
|
||||
// for ringbuffer
|
||||
static uint8_t rbrxbuf[USARTRXBUFSZ], rbtxbuf[USARTTXBUFSZ];
|
||||
static ringbuffer TxRB = {.data = rbtxbuf, .length = USARTTXBUFSZ};
|
||||
static ringbuffer RxRB = {.data = rbrxbuf, .length = USARTRXBUFSZ};
|
||||
|
||||
#define USART_BRR(speed) ((SysFreq + (speed)/2) / (speed))
|
||||
|
||||
static void reinit_rx_dma(){
|
||||
dma_read_idx = 0;
|
||||
RB_clearbuf(&RxRB);
|
||||
DMACHRX->CCR = DMARXCCR; // stop to reload
|
||||
DMACHRX->CNDTR = USARTRXDMABUFSZ;
|
||||
DMACHRX->CMAR = (uint32_t) dmarxbuf;
|
||||
DMACHRX->CCR = DMARXCCR | DMA_CCR_EN;
|
||||
}
|
||||
|
||||
void usart_setup(uint32_t speed){
|
||||
RCC->AHB1ENR |= DMAxEN; // enable DMA
|
||||
// enable USART clocking
|
||||
RCC->USARTxAPB |= USARTxEN;
|
||||
// baudrate
|
||||
USARTx->BRR = USART_BRR(speed);
|
||||
// eol character: '/n'
|
||||
USARTx->CR2 = USART_CR2_ADD_VAL('\n');
|
||||
// enable DMA transmission
|
||||
USARTx->CR3 = USART_CR3_DMAT | USART_CR3_DMAR;
|
||||
// set up DMA channels
|
||||
// Tx channel: mem++, mem->periph, 8bit, compl.&err. irq
|
||||
DMACHTX->CCR = DMATXCCR;
|
||||
DMACHTX->CPAR = (uint32_t) &USARTx->TDR; // peripherial address
|
||||
// Rx channel: mem++, periph->mem, 8bit, compl.&err. irq
|
||||
DMACHRX->CCR = DMARXCCR;
|
||||
DMACHRX->CPAR = (uint32_t) &USARTx->RDR; // peripherial address
|
||||
// set up DMAMUX channels
|
||||
// enumeration of DMAMUX starts from 0 (DMA - from 1)!
|
||||
DMAMUXRX->CCR = DMAMUXRXN;
|
||||
DMAMUXTX->CCR = DMAMUXTXN;
|
||||
// charmatch interrupt, enable transmitter and receiver, enable usart
|
||||
USARTx->CR1 = USART_CR1_CMIE | USART_CR1_TE | USART_CR1_RE | USART_CR1_UE;
|
||||
USARTx->ICR = 0xffffffff; // clear all flags
|
||||
reinit_rx_dma();
|
||||
NVIC_EnableIRQ(USARTIRQn);
|
||||
NVIC_EnableIRQ(DMARXIRQ);
|
||||
NVIC_EnableIRQ(DMATXIRQ);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief usart_sendbuf - send next data portion
|
||||
* @return true if sent something
|
||||
*/
|
||||
static uint8_t usart_sendbuf(){
|
||||
if(!txrdy) return 0;
|
||||
int rd = RB_read(&TxRB, dmatxbuf, USARTTXDMABUFSZ);
|
||||
if(rd < 1) return 0; // nothing to write or busy
|
||||
// set up DMA
|
||||
DMACHTX->CCR = DMATXCCR;
|
||||
DMACHTX->CMAR = (uint32_t) dmatxbuf;
|
||||
DMACHTX->CNDTR = rd;
|
||||
USARTx->ICR = USART_ICR_TCCF; // clear TC flag
|
||||
txrdy = 0;
|
||||
// activate DMA
|
||||
DMACHTX->CCR = DMATXCCR | DMA_CCR_EN;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int usart_send(const char *str, int len){
|
||||
if(!str || len < 1) return 0;
|
||||
uint32_t t = Tms;
|
||||
int sent = 0;
|
||||
do{
|
||||
IWDG->KR = IWDG_REFRESH;
|
||||
int put = RB_write(&TxRB, (uint8_t*)str, len);
|
||||
if(put < 0) continue; // busy
|
||||
else if(put == 0){
|
||||
usart_sendbuf(); // no place
|
||||
t = Tms;
|
||||
}else{
|
||||
len -= put;
|
||||
sent += put;
|
||||
str += put;
|
||||
}
|
||||
}while(len && (Tms - t) < USARTBLKTMOUT); // not more than `block` ms!
|
||||
return sent;
|
||||
}
|
||||
|
||||
int usart_sendstr(const char *str){
|
||||
int l = strlen(str);
|
||||
return usart_send(str, l);
|
||||
}
|
||||
|
||||
static void addtoreadidx(int adder){
|
||||
dma_read_idx += adder;
|
||||
if(dma_read_idx >= USARTRXDMABUFSZ) dma_read_idx -= USARTRXDMABUFSZ;
|
||||
}
|
||||
|
||||
// return current flags
|
||||
USART_flags_t usart_process(){
|
||||
static uint32_t Tlast = 0;
|
||||
USART_flags_t flags = curflags;
|
||||
curflags.all = 0;
|
||||
if(RB_datalento(&TxRB, '\n') > 1 || Tms - Tlast >= USARTSENDTMOUT){ // send buffer as we found '\n' or each 10ms
|
||||
if(usart_sendbuf()) Tlast = Tms;
|
||||
}
|
||||
int remained = DMACHRX->CNDTR;
|
||||
int write_idx = USARTRXDMABUFSZ - remained; // next symbol to be written
|
||||
int available = (write_idx - dma_read_idx); // length of data available
|
||||
if(available < 0) available += USARTRXDMABUFSZ; // write to the left of read
|
||||
if(available == 0) return flags;
|
||||
// add next data portion to RX ring buffer
|
||||
if(available >= (USARTRXDMABUFSZ / 2) || gotstring){
|
||||
// copy data in one or two chunks (wrap handling)
|
||||
// check if we can write to RB `available` bytes
|
||||
int rballow = RxRB.length - 1 - RB_datalen(&RxRB);
|
||||
if(rballow < available){
|
||||
if(available > USARTRXDMABUFSZ - 2){ // near overfull
|
||||
flags.rxovrfl = 1;
|
||||
reinit_rx_dma();
|
||||
RB_clearbuf(&RxRB);
|
||||
return flags;
|
||||
}
|
||||
if(rballow < 1) return flags;
|
||||
available = rballow; // read at least as we can
|
||||
}
|
||||
if(dma_read_idx + available <= USARTRXDMABUFSZ){ // head before tail
|
||||
int written = RB_write(&RxRB, &dmarxbuf[dma_read_idx], available);
|
||||
if(written == available && dmarxbuf[dma_read_idx+available-1] == '\n') gotstring = 0;
|
||||
if(written > 0) addtoreadidx(written);
|
||||
}else{ // head after tail - two chunks
|
||||
int first = USARTRXDMABUFSZ - dma_read_idx;
|
||||
int written = RB_write(&RxRB, &dmarxbuf[dma_read_idx], first);
|
||||
if(written != first){ // could write only part - just increase read index
|
||||
if(written > 0) addtoreadidx(written);
|
||||
}else{
|
||||
dma_read_idx = 0;
|
||||
int last = available - first;
|
||||
written = RB_write(&RxRB, dmarxbuf, last);
|
||||
if(written == last && dmarxbuf[last-1] == '\n') gotstring = 0;
|
||||
if(written > 0) addtoreadidx(written);
|
||||
}
|
||||
}
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
char *usart_getline(){
|
||||
static char buff[MAX_INPLEN];
|
||||
int l = RB_datalento(&RxRB, '\n');
|
||||
if(l < 1){
|
||||
l = RB_datalen(&RxRB); // Rx ringbuffer could be near overflow but without '\n'
|
||||
if(l < MAX_INPLEN-1) return NULL; // allow to wait for last symbols
|
||||
}
|
||||
if(l > MAX_INPLEN-1){ // overflow -> read at least part of the string
|
||||
l = MAX_INPLEN-1;
|
||||
}
|
||||
if(l != RB_read(&RxRB, (uint8_t*)buff, l)) return NULL;
|
||||
buff[l] = 0; // return with '\n' at end of line (so user can detect non-finished overflowed lines)
|
||||
return buff;
|
||||
}
|
||||
|
||||
// interrupt by '\n'
|
||||
static void usart_isr(){
|
||||
if(USARTx->ISR & USART_ISR_CMF){ // got '\n' @ USARTx
|
||||
gotstring = 1;
|
||||
}
|
||||
USARTx->ICR = 0xffffffff; // clear all flags
|
||||
}
|
||||
|
||||
static void dmarx_isr(){
|
||||
volatile uint32_t isr = DMAx->ISR;
|
||||
if(isr & DMARXEF){ // error
|
||||
reinit_rx_dma();
|
||||
curflags.rxovrfl = 1;
|
||||
}
|
||||
DMAx->IFCR = DMARXEF; // clear all flags
|
||||
}
|
||||
|
||||
static void dmatx_isr(){
|
||||
volatile uint32_t isr = DMAx->ISR;
|
||||
if(isr & DMATXTCF) txrdy = 1;
|
||||
if(isr & DMATXEF) curflags.txerr = 1;
|
||||
DMAx->IFCR = DMATXTCF | DMATXEF; // clear all flags
|
||||
}
|
||||
49
G4:G431/USB_CDC/usart.h
Normal file
49
G4:G431/USB_CDC/usart.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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>
|
||||
|
||||
// maximal length of input string (including '\n' and terminating zero)
|
||||
#define MAX_INPLEN (128)
|
||||
// Rx/Tx ring buffer size
|
||||
#define USARTTXBUFSZ (512)
|
||||
#define USARTRXBUFSZ (512)
|
||||
// Rx/Tx DMA buffer size
|
||||
#define USARTTXDMABUFSZ (256)
|
||||
#define USARTRXDMABUFSZ (256)
|
||||
|
||||
// blocking timeout - not more than 5ms
|
||||
#define USARTBLKTMOUT (5)
|
||||
// send buffer each 10ms
|
||||
#define USARTSENDTMOUT (10)
|
||||
|
||||
typedef union{
|
||||
struct{
|
||||
uint8_t txerr : 1; // transmit error
|
||||
uint8_t rxovrfl : 1; // receive buffer overflow
|
||||
};
|
||||
uint8_t all;
|
||||
} USART_flags_t;
|
||||
|
||||
void usart_setup(uint32_t speed);
|
||||
int usart_send(const char *str, int len);
|
||||
char *usart_getline();
|
||||
int usart_sendstr(const char *str);
|
||||
USART_flags_t usart_process();
|
||||
1
G4:G431/USB_CDC/usb.cflags
Normal file
1
G4:G431/USB_CDC/usb.cflags
Normal file
@@ -0,0 +1 @@
|
||||
-std=c23
|
||||
3
G4:G431/USB_CDC/usb.config
Normal file
3
G4:G431/USB_CDC/usb.config
Normal file
@@ -0,0 +1,3 @@
|
||||
#define EBUG
|
||||
#define STM32G4
|
||||
#define STM32G431xx
|
||||
1
G4:G431/USB_CDC/usb.creator
Normal file
1
G4:G431/USB_CDC/usb.creator
Normal file
@@ -0,0 +1 @@
|
||||
[General]
|
||||
1
G4:G431/USB_CDC/usb.cxxflags
Normal file
1
G4:G431/USB_CDC/usb.cxxflags
Normal file
@@ -0,0 +1 @@
|
||||
-std=c++23
|
||||
14
G4:G431/USB_CDC/usb.files
Normal file
14
G4:G431/USB_CDC/usb.files
Normal file
@@ -0,0 +1,14 @@
|
||||
dbg.h
|
||||
hardware.c
|
||||
hardware.h
|
||||
main.c
|
||||
ringbuffer.c
|
||||
ringbuffer.h
|
||||
usart.c
|
||||
usart.h
|
||||
usb_descr.c
|
||||
usb_descr.h
|
||||
usb_dev.c
|
||||
usb_dev.h
|
||||
usb_lib.c
|
||||
usb_lib.h
|
||||
4
G4:G431/USB_CDC/usb.includes
Normal file
4
G4:G431/USB_CDC/usb.includes
Normal file
@@ -0,0 +1,4 @@
|
||||
.
|
||||
../inc
|
||||
../inc/Fx
|
||||
../inc/cm
|
||||
210
G4:G431/USB_CDC/usb_descr.c
Normal file
210
G4:G431/USB_CDC/usb_descr.c
Normal file
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* 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 "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.2");
|
||||
_USB_STRING_(MD, u"eddy@sao.ru");
|
||||
_USB_STRING_(PD, u"USB-CDC");
|
||||
_USB_STRING_(ID, u"usbcdc");
|
||||
|
||||
static const void* const StringDescriptor[iDESCR_AMOUNT] = {
|
||||
[iLANGUAGE_DESCR] = &LD,
|
||||
[iMANUFACTURER_DESCR] = &MD,
|
||||
[iPRODUCT_DESCR] = &PD,
|
||||
[iSERIAL_DESCR] = &SD,
|
||||
[iINTERFACE_DESCR1] = &ID
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
64
G4:G431/USB_CDC/usb_descr.h
Normal file
64
G4:G431/USB_CDC/usb_descr.h
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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
|
||||
// Rx buffer should be not more than 64 and multiple of 2 (or 4 for STM32G0), or multiple of 32
|
||||
// Tx buffer should be multiple of 2 (or 4 for STM32G0)
|
||||
// for USB FS EP0 buffers are from 8 to 64 bytes long
|
||||
#define USB_EP0BUFSZ 64
|
||||
#define USB_EP1BUFSZ 10
|
||||
// Rx/Tx EPs (not more than 64 bytes for CDC)
|
||||
#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);
|
||||
256
G4:G431/USB_CDC/usb_dev.c
Normal file
256
G4:G431/USB_CDC/usb_dev.c
Normal file
@@ -0,0 +1,256 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
//#pragma GCC optimize ("O1")
|
||||
|
||||
// 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();
|
||||
}
|
||||
if(lastdsz == 0 && byte == '\n') send_next();
|
||||
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
|
||||
*/
|
||||
//__attribute__((noinline, noclone, optimize("O0")))
|
||||
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 < 1) return 0;
|
||||
buf[l-1] = 0; // replace '\n' with strend
|
||||
return l;
|
||||
}
|
||||
|
||||
57
G4:G431/USB_CDC/usb_dev.h
Normal file
57
G4:G431/USB_CDC/usb_dev.h
Normal 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);
|
||||
454
G4:G431/USB_CDC/usb_lib.c
Normal file
454
G4:G431/USB_CDC/usb_lib.c
Normal file
@@ -0,0 +1,454 @@
|
||||
/*
|
||||
* 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 "dbg.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
|
||||
for(int i = 0; i < N2; ++i){
|
||||
endpoints[number].tx_buf[i] = buf16[i];
|
||||
}
|
||||
#elif defined USB32
|
||||
// use memcpy instead?
|
||||
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)()){
|
||||
#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
|
||||
//DBGs("EP_init: TXsz="); DBGs(u2str(txsz));
|
||||
//DBGs("; RXsz="); DBGs(u2str(rxsz)); DBGn();
|
||||
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)){
|
||||
DBGs("Can't init\n");
|
||||
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 || defined STM32G4
|
||||
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 || defined STM32G4
|
||||
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){ DBGl("HSI not ready!"); 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;
|
||||
#elif defined STM32G4
|
||||
NVIC_DisableIRQ(USB_LP_IRQn);
|
||||
// HSI48 & SOF sync already enabled in StartHSE()
|
||||
#endif
|
||||
#ifndef STM32G0
|
||||
#ifdef STM32G4
|
||||
RCC->APB1ENR1 |= RCC_APB1ENR1_USBEN;
|
||||
#else
|
||||
RCC->APB1ENR |= RCC_APB1ENR_USBEN;
|
||||
#endif
|
||||
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);
|
||||
#elif defined STM32G4
|
||||
NVIC_EnableIRQ(USB_LP_IRQn);
|
||||
USB->BCDR |= USB_BCDR_DPPU;
|
||||
#endif
|
||||
DBGl("USB ready");
|
||||
}
|
||||
|
||||
|
||||
#if defined STM32F3 || defined STM32G4
|
||||
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
|
||||
352
G4:G431/USB_CDC/usb_lib.h
Normal file
352
G4:G431/USB_CDC/usb_lib.h
Normal 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>
|
||||
#elif defined STM32G4
|
||||
#include <stm32g4.h>
|
||||
#endif
|
||||
|
||||
// max endpoints number
|
||||
#define STM32ENDPOINTS 8
|
||||
/**
|
||||
* Buffers size definition
|
||||
**/
|
||||
|
||||
// F0, G4 - USB2_16 (2 half-words by 16 bits in each 32-bit word)
|
||||
// F1 - USB1_16 (1 half-word in each 32-bit word)
|
||||
// F3 - 1/2 depending on series
|
||||
// G0 - USB32 (1 32-bit word in each 32-bit word)
|
||||
#if !defined USB1_16 && !defined USB2_16 && !defined USB32
|
||||
#if defined STM32F0 || defined STM32G4
|
||||
#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 || defined STM32F3
|
||||
#define USB_BTABLE_SIZE 1024
|
||||
#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 || 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 or STM32G4 - 1024 bytes (512 half-words by 16 bit)
|
||||
#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 STM32G4 || 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();
|
||||
BIN
G4:G431/USB_CDC/usbcdc.bin
Executable file
BIN
G4:G431/USB_CDC/usbcdc.bin
Executable file
Binary file not shown.
@@ -49,20 +49,19 @@ LIB_DIR := $(INC_DIR)/ld
|
||||
|
||||
###############################################################################
|
||||
# C flags
|
||||
CFLAGS += -D__thumb2__=1 -MD
|
||||
CFLAGS += -Wall -Wextra -Wshadow -Wdouble-promotion
|
||||
CFLAGS += -fshort-enums -ffunction-sections -fdata-sections
|
||||
#CFLAGS += -fno-common -fno-stack-protector
|
||||
CFLAGS += $(ARCH_FLAGS)
|
||||
FLAGS := -D__thumb2__=1 -MD -Wall -Wextra -Wshadow -Wdouble-promotion -fshort-enums -ffunction-sections -fdata-sections
|
||||
# -fno-common -fno-stack-protector
|
||||
CFLAGS += $(FLAGS) $(ARCH_FLAGS)
|
||||
CPPFLAGS += $(FLAGS) $(ARCH_FLAGS)
|
||||
|
||||
###############################################################################
|
||||
# Linker flags
|
||||
LDFLAGS += -nostartfiles --static -specs=nosys.specs -specs=nano.specs
|
||||
LDFLAGS += -nostartfiles --static -specs=nano.specs
|
||||
LDFLAGS += $(ARCH_FLAGS)
|
||||
LDFLAGS += -L$(LIB_DIR)
|
||||
#-L$(TOOLCHLIB)
|
||||
LDFLAGS += -T$(LDSCRIPT)
|
||||
LDFLAGS += -Wl,-Map=$(MAP),--cref -Wl,--gc-sections -Wl,--print-memory-usage
|
||||
LDFLAGS += -Wl,-Map=$(MAP),--cref -Wl,--gc-sections -Wl,--print-memory-usage -Wl,--trace-symbol=_sbrk
|
||||
|
||||
###############################################################################
|
||||
# Used libraries
|
||||
@@ -102,6 +101,8 @@ debug: CFLAGS += -O0 -DEBUG -Werror -g3 -gdwarf-2
|
||||
debug: TARGET := DEBUG
|
||||
debug: $(TARGFILE) bin list size
|
||||
|
||||
all: $(BIN)
|
||||
|
||||
$(TARGFILE): $(OBJDIR)
|
||||
@echo -e "\t\tTARGET: $(TARGET)"
|
||||
@echo "$(TARGET)" > $(TARGFILE)
|
||||
@@ -128,15 +129,19 @@ $(VERSION_FILE): *.[ch]
|
||||
@sed -i "s/#define BUILDNO.*/#define BUILDNO $(NEXTVER)/" $(VERSION_FILE)
|
||||
@sed -i "s/#define BUILD_DATE.*/#define BUILD_DATE \"$(BUILDDATE)\"/" $(VERSION_FILE)
|
||||
|
||||
$(OBJDIR)/commproto.o: commproto.cpp $(VERSION_FILE)
|
||||
|
||||
$(OBJDIR)/proto.o: proto.c $(VERSION_FILE)
|
||||
|
||||
$(OBJDIR)/usb_dev.o: CFLAGS := $(filter-out -flto,$(CFLAGS)) -fno-lto
|
||||
|
||||
$(OBJDIR)/%.o: %.c
|
||||
@echo " CC $<"
|
||||
$(CC) $(CFLAGS) $(DEFS) $(INCLUDE) -o $@ -c $<
|
||||
|
||||
$(OBJDIR)/%.o: %.cpp
|
||||
@echo " C++ $<"
|
||||
$(CPP) -c $(CFLAGS) -fno-exceptions $(DEFS) $(INCLUDE) -o $@ -c $<
|
||||
$(CPP) -c $(CPPFLAGS) -fno-exceptions -fno-rtti -Wl,--trace-symbol=_sbrk $(DEFS) $(INCLUDE) -o $@ -c $<
|
||||
|
||||
$(BIN): $(ELF)
|
||||
@echo " OBJCOPY $(BIN)"
|
||||
|
||||
Reference in New Issue
Block a user