Compare commits

...

9 Commits

Author SHA1 Message Date
Edward Emelianov
811d8bda5d add LST 2026-08-12 22:35:39 +03:00
Edward Emelianov
40d0605218 . 2026-08-04 19:35:32 +03:00
Edward Emelianov
970d73dc38 add CORDIC 2026-08-04 18:22:58 +03:00
Edward Emelianov
b8f3e5a695 throw out interrupts 2026-07-29 23:06:36 +03:00
Edward Emelianov
940b60df99 add flash storage 2026-07-29 21:01:01 +03:00
Edward Emelianov
2b4a9cbd84 simplest servo 2026-07-28 23:17:03 +03:00
Edward Emelianov
f6178a8b84 add plot script 2026-07-23 22:29:10 +03:00
Edward Emelianov
9fb0956dcf fixed -flto problem (usb_dev.c needs -fno-lto) 2026-07-23 21:15:11 +03:00
Edward Emelianov
8252d370a2 (works only without -flto) 2026-07-23 20:42:08 +03:00
81 changed files with 6170 additions and 16 deletions

View File

@@ -94,7 +94,7 @@ static int binarySearch(int r, const uint8_t *start, int stor_size){
*/ */
void flashstorage_init(){ void flashstorage_init(){
if(FLASH_SIZE > 0 && FLASH_SIZE < 20000){ 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; flsz -= (uint32_t)(&__varsstart) - FLASH_BASE;
maxCnum = flsz / sizeof(user_conf); 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; uint32_t nblocks = 1, flsz = 0;
if(!end){ // erase all remaining if(!end){ // erase all remaining
if(FLASH_SIZE > 0 && FLASH_SIZE < 20000){ 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; flsz -= (uint32_t)start - FLASH_BASE;
} }
}else{ // erase a part }else{ // erase a part

View File

@@ -96,7 +96,7 @@ static int binarySearch(int r, const uint8_t *start, int stor_size){
*/ */
void flashstorage_init(){ void flashstorage_init(){
if(FLASH_SIZE > 0 && FLASH_SIZE < 20000){ 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; flsz -= (uint32_t)(&__varsstart) - FLASH_BASE;
maxCnum = flsz / sizeof(user_conf); maxCnum = flsz / sizeof(user_conf);
//SEND("flsz="); printu(flsz); //SEND("flsz="); printu(flsz);
@@ -165,7 +165,7 @@ static int erase_flash(const void *start, const void *end){
uint32_t nblocks = 1, flsz = 0; uint32_t nblocks = 1, flsz = 0;
if(!end){ // erase all remaining if(!end){ // erase all remaining
if(FLASH_SIZE > 0 && FLASH_SIZE < 20000){ 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; flsz -= (uint32_t)start - FLASH_BASE;
} }
}else{ // erase a part }else{ // erase a part

View File

@@ -103,7 +103,7 @@ static int binarySearch(int r, const uint8_t *start, int stor_size){
*/ */
void flashstorage_init(){ void flashstorage_init(){
if(FLASH_SIZE > 0 && FLASH_SIZE < 20000){ 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; flsz -= (uint32_t)(&__varsstart) - FLASH_BASE;
maxCnum = flsz / sizeof(user_conf); 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; uint32_t nblocks = 1, flsz = 0;
if(!end){ // erase all remaining if(!end){ // erase all remaining
if(FLASH_SIZE > 0 && FLASH_SIZE < 20000){ 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; flsz -= (uint32_t)start - FLASH_BASE;
} }
}else{ // erase a part }else{ // erase a part

View 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

View File

@@ -81,7 +81,7 @@ static int binarySearch(int r, const uint8_t *start, int stor_size){
*/ */
void flashstorage_init(){ void flashstorage_init(){
if(FLASH_SIZE > 0 && FLASH_SIZE < 20000){ 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; flsz -= (uint32_t)(&__varsstart) - FLASH_BASE;
maxCnum = flsz / sizeof(user_conf); maxCnum = flsz / sizeof(user_conf);
} }
@@ -149,7 +149,7 @@ static int erase_flash(const void *start, const void *end){
if(!start) return 1; if(!start) return 1;
uint32_t startb = (((uint32_t)start - FLASH_BASE) + blocksize - 1) / blocksize, endb; uint32_t startb = (((uint32_t)start - FLASH_BASE) + blocksize - 1) / blocksize, endb;
if(!end){ // erase all remaining if(!end){ // erase all remaining
endb = FLASH_SIZE / blocksize; endb = FLASH_SIZE * 1024 / blocksize;
}else{ // erase a part }else{ // erase a part
endb = (((uint32_t)end - FLASH_BASE) + blocksize - 1) / blocksize; endb = (((uint32_t)end - FLASH_BASE) + blocksize - 1) / blocksize;
} }

13
G4:G431/CORDIC/Makefile Normal file
View 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
View 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
View 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);

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

View 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

Binary file not shown.

151
G4:G431/CORDIC/cordic.c Normal file
View 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;
}

View File

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

View File

@@ -0,0 +1,3 @@
#define EBUG
#define STM32G4
#define STM32G431xx

View File

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

View File

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

View 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
View 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);

View File

@@ -0,0 +1,4 @@
.
../inc
../inc/Fx
../inc/cm

69
G4:G431/CORDIC/hardware.c Normal file
View 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
View 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
View 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");
}
}
}
}

View 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
View File

@@ -0,0 +1,203 @@
/*
* Copyright 2023 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include <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;
}

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

View File

@@ -0,0 +1,2 @@
#define BUILD_NUMBER "48"
#define BUILD_DATE "2026-08-12"

12
G4:G431/Servo/Makefile Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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");
}
}
}
}

View 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
View File

@@ -0,0 +1,203 @@
/*
* Copyright 2023 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include <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;
}

View 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

Binary file not shown.

84
G4:G431/Servo/servo.c Normal file
View 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;
}

View File

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

View File

@@ -0,0 +1,3 @@
#define EBUG
#define STM32G4
#define STM32G431xx

View File

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

View File

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

15
G4:G431/Servo/servo.files Normal file
View 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
View 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);

View File

@@ -0,0 +1,4 @@
.
../inc
../inc/Fx
../inc/cm

265
G4:G431/Servo/strfunc.c Normal file
View 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
View 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
View 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
View 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();

View File

@@ -0,0 +1,2 @@
#define BUILD_NUMBER "28"
#define BUILD_DATE "2026-07-29"

11
G4:G431/USB_CDC/Makefile Normal file
View 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
View 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

View 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
}

View 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
View 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();
}
}
}

View File

@@ -0,0 +1,4 @@
set FLASH_SIZE 0x20000
source [find interface/stlink-v2-1.cfg]
source [find target/stm32g4x.cfg]

View File

@@ -0,0 +1,203 @@
/*
* Copyright 2023 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include <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;
}

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

View File

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

View File

@@ -0,0 +1,3 @@
#define EBUG
#define STM32G4
#define STM32G431xx

View File

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

View File

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

14
G4:G431/USB_CDC/usb.files Normal file
View 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

View File

@@ -0,0 +1,4 @@
.
../inc
../inc/Fx
../inc/cm

210
G4:G431/USB_CDC/usb_descr.c Normal file
View 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;
}
}

View 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
View 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
View File

@@ -0,0 +1,57 @@
/*
* Copyright 2024 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdint.h>
#include "usb_lib.h"
typedef struct {
uint32_t dwDTERate;
uint8_t bCharFormat;
#define USB_CDC_1_STOP_BITS 0
#define USB_CDC_1_5_STOP_BITS 1
#define USB_CDC_2_STOP_BITS 2
uint8_t bParityType;
#define USB_CDC_NO_PARITY 0
#define USB_CDC_ODD_PARITY 1
#define USB_CDC_EVEN_PARITY 2
#define USB_CDC_MARK_PARITY 3
#define USB_CDC_SPACE_PARITY 4
uint8_t bDataBits;
} __attribute__ ((packed)) usb_LineCoding;
extern usb_LineCoding lineCoding;
extern volatile uint8_t CDCready;
void break_handler();
void clstate_handler(uint16_t val);
void linecoding_handler(usb_LineCoding *lc);
// sizes of ringbuffers for outgoing and incoming data
#define RBOUTSZ (1024)
#define RBINSZ (1024)
#define newline() USB_putbyte('\n')
#define USND(s) do{USB_sendstr(s); USB_putbyte('\n');}while(0)
int USB_sendall();
int USB_send(const uint8_t *buf, int len);
int USB_putbyte(uint8_t byte);
int USB_sendstr(const char *string);
int USB_receive(uint8_t *buf, int len);
int USB_receivestr(char *buf, int len);

454
G4:G431/USB_CDC/usb_lib.c Normal file
View 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
View File

@@ -0,0 +1,352 @@
/*
* Copyright 2024 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdint.h>
#include <wchar.h>
#ifndef _U_
#define _U_ __attribute__((unused))
#endif
/******************************************************************
* Hardware registers etc *
*****************************************************************/
#if defined STM32F0
#include <stm32f0.h>
#elif defined STM32F1
#include <stm32f1.h>
// there's no this define in standard header
#define USB_BASE ((uint32_t)0x40005C00)
#elif defined STM32F3
#include <stm32f3.h>
#elif defined STM32G0
#include <stm32g0.h>
#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

Binary file not shown.

View File

@@ -49,20 +49,19 @@ LIB_DIR := $(INC_DIR)/ld
############################################################################### ###############################################################################
# C flags # C flags
CFLAGS += -D__thumb2__=1 -MD FLAGS := -D__thumb2__=1 -MD -Wall -Wextra -Wshadow -Wdouble-promotion -fshort-enums -ffunction-sections -fdata-sections
CFLAGS += -Wall -Wextra -Wshadow -Wdouble-promotion # -fno-common -fno-stack-protector
CFLAGS += -fshort-enums -ffunction-sections -fdata-sections CFLAGS += $(FLAGS) $(ARCH_FLAGS)
#CFLAGS += -fno-common -fno-stack-protector CPPFLAGS += $(FLAGS) $(ARCH_FLAGS)
CFLAGS += $(ARCH_FLAGS)
############################################################################### ###############################################################################
# Linker flags # Linker flags
LDFLAGS += -nostartfiles --static -specs=nosys.specs -specs=nano.specs LDFLAGS += -nostartfiles --static -specs=nano.specs
LDFLAGS += $(ARCH_FLAGS) LDFLAGS += $(ARCH_FLAGS)
LDFLAGS += -L$(LIB_DIR) LDFLAGS += -L$(LIB_DIR)
#-L$(TOOLCHLIB) #-L$(TOOLCHLIB)
LDFLAGS += -T$(LDSCRIPT) 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 # Used libraries
@@ -102,6 +101,8 @@ debug: CFLAGS += -O0 -DEBUG -Werror -g3 -gdwarf-2
debug: TARGET := DEBUG debug: TARGET := DEBUG
debug: $(TARGFILE) bin list size debug: $(TARGFILE) bin list size
all: $(BIN)
$(TARGFILE): $(OBJDIR) $(TARGFILE): $(OBJDIR)
@echo -e "\t\tTARGET: $(TARGET)" @echo -e "\t\tTARGET: $(TARGET)"
@echo "$(TARGET)" > $(TARGFILE) @echo "$(TARGET)" > $(TARGFILE)
@@ -128,15 +129,19 @@ $(VERSION_FILE): *.[ch]
@sed -i "s/#define BUILDNO.*/#define BUILDNO $(NEXTVER)/" $(VERSION_FILE) @sed -i "s/#define BUILDNO.*/#define BUILDNO $(NEXTVER)/" $(VERSION_FILE)
@sed -i "s/#define BUILD_DATE.*/#define BUILD_DATE \"$(BUILDDATE)\"/" $(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)/proto.o: proto.c $(VERSION_FILE)
$(OBJDIR)/usb_dev.o: CFLAGS := $(filter-out -flto,$(CFLAGS)) -fno-lto
$(OBJDIR)/%.o: %.c $(OBJDIR)/%.o: %.c
@echo " CC $<" @echo " CC $<"
$(CC) $(CFLAGS) $(DEFS) $(INCLUDE) -o $@ -c $< $(CC) $(CFLAGS) $(DEFS) $(INCLUDE) -o $@ -c $<
$(OBJDIR)/%.o: %.cpp $(OBJDIR)/%.o: %.cpp
@echo " C++ $<" @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) $(BIN): $(ELF)
@echo " OBJCOPY $(BIN)" @echo " OBJCOPY $(BIN)"