SPI1/2 works

This commit is contained in:
Edward Emelianov 2025-03-26 17:56:34 +03:00
parent c4529a8447
commit 33497f79b5
18 changed files with 763 additions and 33 deletions

View File

@ -0,0 +1,4 @@
ACTION=="add", DRIVERS=="usb", ENV{USB_IDS}="%s{idVendor}:%s{idProduct}"
ACTION=="add", ENV{USB_IDS}=="067b:2303", ATTRS{interface}=="?*", PROGRAM="/bin/bash -c \"ls /dev | grep $attr{interface} | wc -l \"", SYMLINK+="$attr{interface}%c", MODE="0666", GROUP="tty"
ACTION=="add", ENV{USB_IDS}=="0483:5740", ATTRS{interface}=="?*", PROGRAM="/bin/bash -c \"ls /dev | grep $attr{interface} | wc -l \"", SYMLINK+="$attr{interface}%c", MODE="0666", GROUP="tty"

Binary file not shown.

View File

@ -1,3 +1,5 @@
flash.c
flash.h
hardware.c
hardware.h
main.c
@ -5,6 +7,8 @@ proto.c
proto.h
ringbuffer.c
ringbuffer.h
spi.c
spi.h
strfunc.c
strfunc.h
usart.c

View File

@ -0,0 +1,196 @@
/*
* This file is part of the encoders project.
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "stm32f1.h"
#include "flash.h"
#include "strfunc.h"
#include "usb_dev.h" // printout
#include <string.h> // memcpy
extern const uint32_t __varsstart, _BLOCKSIZE;
static const uint32_t FLASH_blocksize = (uint32_t)&_BLOCKSIZE;
static uint32_t maxCnum = 1024 / sizeof(user_conf); // can't use blocksize here
#define USERCONF_INITIALIZER { \
.userconf_sz = sizeof(user_conf) \
}
static int write2flash(const void*, const void*, 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;
#ifdef EBUG
CMDWR("mid/l/r=");
CMDWR(u2str(mid)); CMDWR("/");
CMDWR(u2str(l)); CMDWR("/");
CMDWR(u2str(r)); CMDWR("/");
CMDn();
#endif
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);
}
#ifdef EBUG
CMDWR("INIT\n");
#endif
// -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));
}
#ifdef EBUG
CMDWR("currentconfidx="); CMDWR(i2str(currentconfidx)); CMDn();
#endif
}
// store new configuration
// @return 0 if all OK
int store_userconf(){
// maxnum - 3 means that there always should be at least one empty record after last data
// for binarySearch() checking that there's nothing more after it!
if(currentconfidx > (int)maxCnum - 3){ // there's no more place
currentconfidx = 0;
if(erase_storage(-1)) return 1;
}else ++currentconfidx; // take next data position (0 - within first run after firmware flashing)
return write2flash((const void*)&Flash_Data[currentconfidx], &the_conf, sizeof(the_conf));
}
static int write2flash(const void *start, const void *wrdata, uint32_t stor_size){
int ret = 0;
if (FLASH->CR & FLASH_CR_LOCK){ // unloch flash
FLASH->KEYR = FLASH_KEY1;
FLASH->KEYR = FLASH_KEY2;
}
while (FLASH->SR & FLASH_SR_BSY) IWDG->KR = IWDG_REFRESH;
FLASH->SR = FLASH_SR_EOP | FLASH_SR_PGERR | FLASH_SR_WRPRTERR; // clear all flags
FLASH->CR |= FLASH_CR_PG;
const uint16_t *data = (const uint16_t*) wrdata;
volatile uint16_t *address = (volatile uint16_t*) start;
uint32_t i, count = (stor_size + 1) / 2;
for(i = 0; i < count; ++i){
*(volatile uint16_t*)(address + i) = data[i];
while(FLASH->SR & FLASH_SR_BSY) IWDG->KR = IWDG_REFRESH;
if(*(volatile uint16_t*)(address + i) != data[i]){
CMDWR("DON'T MATCH!\n");
ret = 1;
break;
}
if(FLASH->SR & FLASH_SR_PGERR){
CMDWR("Prog err\n");
ret = 1; // program error - meet not 0xffff
break;
}
FLASH->SR = FLASH_SR_EOP | FLASH_SR_PGERR | FLASH_SR_WRPRTERR;
}
FLASH->CR &= ~(FLASH_CR_PG);
return ret;
}
// erase Nth page of flash storage (flash should be prepared!)
static int erase_pageN(int N){
int ret = 0;
#ifdef EBUG
CMDWR("Erase page #"); CMDWR(u2str(N)); CMDn();
#endif
FLASH->AR = (uint32_t)Flash_Data + N*FLASH_blocksize;
FLASH->CR |= FLASH_CR_STRT;
while(FLASH->SR & FLASH_SR_BSY) IWDG->KR = IWDG_REFRESH;
FLASH->SR = FLASH_SR_EOP;
if(FLASH->SR & FLASH_SR_WRPRTERR){ /* Check Write protection error */
ret = 1;
FLASH->SR = FLASH_SR_WRPRTERR; /* Clear the flag by software by writing it at 1*/
}
return ret;
}
// erase full storage (npage < 0) or its nth page; @return 0 if all OK
int erase_storage(int npage){
int ret = 0;
uint32_t end = 1, start = 0, flsz = 0;
if(FLASH_SIZE > 0 && FLASH_SIZE < 20000){
flsz = FLASH_SIZE * 1024; // size in bytes
flsz -= (uint32_t)Flash_Data - FLASH_BASE;
}
end = flsz / FLASH_blocksize;
#ifdef EBUG
CMDWR("FLASH_SIZE="); CMDWR(u2str(FLASH_SIZE));
CMDWR("\nflsz="); CMDWR(u2str(flsz));
CMDWR("\nend="); CMDWR(u2str(end));
CMDWR("\ncurrentconfidx="); CMDWR(u2str(currentconfidx));
CMDWR("\nmaxCnum="); CMDWR(u2str(maxCnum));
CMDn();
#endif
if(end == 0 || end >= FLASH_SIZE) return 1;
if(npage > -1){ // erase only one page
if((uint32_t)npage >= end) return 1;
start = npage;
end = start + 1;
}
if((FLASH->CR & FLASH_CR_LOCK) != 0){
FLASH->KEYR = FLASH_KEY1;
FLASH->KEYR = FLASH_KEY2;
}
while(FLASH->SR & FLASH_SR_BSY) IWDG->KR = IWDG_REFRESH;
FLASH->SR = FLASH_SR_EOP | FLASH_SR_PGERR | FLASH_SR_WRPRTERR;
FLASH->CR |= FLASH_CR_PER;
for(uint32_t i = start; i < end; ++i){
if(erase_pageN(i)){
ret = 1;
break;
}
}
FLASH->CR &= ~FLASH_CR_PER;
return ret;
}

View File

@ -0,0 +1,46 @@
/*
* 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>
#include "usb_descr.h"
#define FLASH_SIZE_REG ((uint32_t)0x1FFFF7E0)
#define FLASH_SIZE *((uint16_t*)FLASH_SIZE_REG)
// maximal size (in letters) of iInterface for settings
#define MAX_IINTERFACE_SZ (16)
/*
* struct to save user configurations
*/
typedef struct __attribute__((packed, aligned(4))){
uint16_t userconf_sz; // "magick number"
uint16_t iInterface[bTotNumEndpoints][MAX_IINTERFACE_SZ]; // hryunikod!
uint8_t iIlengths[bTotNumEndpoints];
} user_conf;
extern user_conf the_conf;
extern int currentconfidx;
void flashstorage_init();
int store_userconf();
int erase_storage(int npage);

View File

@ -16,21 +16,27 @@
*/
#include "hardware.h"
#include "spi.h"
static inline void gpio_setup(){
// Enable clocks to the GPIO subsystems (PB for ADC), turn on AFIO clocking to disable SWD/JTAG
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPCEN | RCC_APB2ENR_AFIOEN;
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_IOPCEN | RCC_APB2ENR_AFIOEN;
// turn off SWJ/JTAG
// AFIO->MAPR = AFIO_MAPR_SWJ_CFG_DISABLE;
AFIO->MAPR = AFIO_MAPR_SWJ_CFG_JTAGDISABLE; // for PA15
// Set led as opendrain output
GPIOC->CRH |= CRH(13, CNF_ODOUTPUT|MODE_SLOW);
GPIOA->CRL = CRL(5, CNF_AFPP|MODE_FAST) | CRL(6, CNF_FLINPUT);
// USB pullup (PA15) - pushpull output
GPIOA->CRH = CRH(15, CNF_PPOUTPUT|MODE_SLOW);
GPIOB->CRH = CRH(13, CNF_AFPP|MODE_FAST) | CRH(14, CNF_FLINPUT);
}
void hw_setup(){
gpio_setup();
// setup both SPI channels
spi_setup(1);
spi_setup(2);
}
#ifndef EBUG

View File

@ -16,8 +16,10 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "flash.h"
#include "hardware.h"
#include "proto.h"
#include "spi.h"
#include "strfunc.h"
#include "usart.h"
#include "usb_dev.h"
@ -32,7 +34,9 @@ void sys_tick_handler(void){
int main(){
char inbuff[RBINSZ];
uint32_t lastT = 0, lastS = 0;
uint8_t encbuf[ENCODER_BUFSZ];
StartHSE();
flashstorage_init();
hw_setup();
USBPU_OFF();
SysTick_Config(72000);
@ -79,6 +83,9 @@ int main(){
CMDWR(inbuff);
CMDWR("'\n");
}
if(spi_read_enc(0, encbuf)){ // send encoder data
hexdump(I_X, encbuf, ENCODER_BUFSZ);
}
}
if(CDCready[I_Y]){
int l = USB_receivestr(I_Y, inbuff, RBINSZ);
@ -88,6 +95,9 @@ int main(){
CMDWR(inbuff);
CMDWR("'\n");
}
if(spi_read_enc(1, encbuf)){ // send encoder data
hexdump(I_Y, encbuf, ENCODER_BUFSZ);
}
}
}
return 0;

View File

@ -1,4 +1,94 @@
set FLASH_SIZE 0x10000
source [find interface/stlink-v2-1.cfg]
source [find target/stm32f1x.cfg]
source [find interface/stlink.cfg]
# script for stm32f1x family
#
# stm32 devices support both JTAG and SWD transports.
#
source [find target/swj-dp.tcl]
source [find mem_helper.tcl]
if { [info exists CHIPNAME] } {
set _CHIPNAME $CHIPNAME
} else {
set _CHIPNAME stm32f1x
}
set _ENDIAN little
# Work-area is a space in RAM used for flash programming
# By default use 4kB (as found on some STM32F100s)
if { [info exists WORKAREASIZE] } {
set _WORKAREASIZE $WORKAREASIZE
} else {
set _WORKAREASIZE 0x1000
}
# Allow overriding the Flash bank size
if { [info exists FLASH_SIZE] } {
set _FLASH_SIZE $FLASH_SIZE
} else {
# autodetect size
set _FLASH_SIZE 0
}
#jtag scan chain
if { [info exists CPUTAPID] } {
set _CPUTAPID $CPUTAPID
} else {
if { [using_jtag] } {
# See STM Document RM0008 Section 26.6.3
set _CPUTAPID 0x2ba01477
} {
# this is the SW-DP tap id not the jtag tap id
set _CPUTAPID 0x2ba01477
}
}
swj_newdap $_CHIPNAME cpu -irlen 4 -ircapture 0x1 -irmask 0xf -expected-id $_CPUTAPID
dap create $_CHIPNAME.dap -chain-position $_CHIPNAME.cpu
if {[using_jtag]} {
jtag newtap $_CHIPNAME bs -irlen 5
}
set _TARGETNAME $_CHIPNAME.cpu
target create $_TARGETNAME cortex_m -endian $_ENDIAN -dap $_CHIPNAME.dap
$_TARGETNAME configure -work-area-phys 0x20000000 -work-area-size $_WORKAREASIZE -work-area-backup 0
# flash size will be probed
set _FLASHNAME $_CHIPNAME.flash
flash bank $_FLASHNAME stm32f1x 0x08000000 $_FLASH_SIZE 0 0 $_TARGETNAME
# JTAG speed should be <= F_CPU/6. F_CPU after reset is 8MHz, so use F_JTAG = 1MHz
adapter_khz 1000
adapter_nsrst_delay 100
if {[using_jtag]} {
jtag_ntrst_delay 100
}
reset_config srst_nogate
if {![using_hla]} {
# if srst is not fitted use SYSRESETREQ to
# perform a soft reset
cortex_m reset_config sysresetreq
}
$_TARGETNAME configure -event examine-end {
# DBGMCU_CR |= DBG_WWDG_STOP | DBG_IWDG_STOP |
# DBG_STANDBY | DBG_STOP | DBG_SLEEP
mmw 0xE0042004 0x00000307 0
}
$_TARGETNAME configure -event trace-config {
# Set TRACE_IOEN; TRACE_MODE is set to async; when using sync
# change this value accordingly to configure trace pins
# assignment
mmw 0xE0042004 0x00000020 0
}

View File

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

View File

@ -18,20 +18,13 @@
#include <string.h>
#include "flash.h"
#include "proto.h"
#include "spi.h"
#include "strfunc.h"
#include "usb_dev.h"
#include "version.inc"
// commands indexes
typedef enum{
C_dummy,
C_help,
C_sendX,
C_sendY,
C_AMOUNT
} cmd_e;
// error codes
typedef enum {
ERR_OK, // no errors
@ -50,6 +43,27 @@ static const char* const errors[ERR_AMOUNT] = {
[ERR_SILENCE] = "",
};
// commands indexes
typedef enum{
C_dummy,
C_help,
C_sendX,
C_sendY,
C_setiface1,
C_setiface2,
C_setiface3,
C_dumpconf,
C_erasestorage,
C_storeconf,
C_reboot,
C_fin,
C_encstart,
C_spistat,
C_spiinit,
C_spideinit,
C_AMOUNT
} cmd_e;
// command handler (idx - index of command in list, par - all after equal sign in "cmd = par")
typedef errcode_e (*handler_t)(cmd_e idx, char *par);
@ -65,6 +79,7 @@ static const funcdescr_t commands[C_AMOUNT];
CMDWR(i2str(ival)); CMDn(); return ERR_SILENCE; }while(0)
static errcode_e help(cmd_e idx, char* par);
static errcode_e dumpconf(cmd_e idx, char *par);
static errcode_e dummy(cmd_e idx, char *par){
static int32_t val = -111;
@ -98,14 +113,133 @@ static errcode_e sendenc(cmd_e idx, char *par){
return ERR_OK;
}
static errcode_e setiface(cmd_e idx, char *par){
if(idx < C_setiface1 || idx >= C_setiface1 + bTotNumEndpoints) return ERR_BADCMD;
idx -= C_setiface1; // now it is an index of iIlengths
if(par && *par){
int l = strlen(par);
DBGs(i2str(l));
if(l > MAX_IINTERFACE_SZ) return ERR_BADPAR; // too long
the_conf.iIlengths[idx] = (uint8_t) l * 2;
char *ptr = (char*)the_conf.iInterface[idx];
for(int i = 0; i < l; ++i){ // make fucking hryunicode
*ptr++ = *par++;
*ptr++ = 0;
}
}
// getter
int l = the_conf.iIlengths[idx] / 2;
char *ptr = (char*)the_conf.iInterface[idx];
CMDWR(commands[idx + C_setiface1].cmd);
CMDWR("=");
for(int i = 0; i < l; ++i){
USB_putbyte(I_CMD, *ptr);
ptr += 2;
}
CMDn();
return ERR_SILENCE;
}
static errcode_e erasestor(cmd_e _U_ idx, char *par){
int32_t npage = -1;
if(par){
if(par == getint(par, &npage)) return ERR_BADPAR;
}
if(erase_storage(npage)) return ERR_FAIL;
return ERR_OK;
}
static errcode_e storeconf(_U_ cmd_e idx, _U_ char *par){
if(store_userconf()) return ERR_FAIL;
return ERR_OK;
}
static errcode_e reboot(_U_ cmd_e idx, _U_ char *par){
NVIC_SystemReset();
return ERR_OK; // never reached
}
static errcode_e fini(_U_ cmd_e idx, _U_ char *par){
flashstorage_init();
return ERR_OK; // never reached
}
static errcode_e encstart(_U_ cmd_e idx, _U_ char *par){
if(!spi_start_enc(0)) return ERR_FAIL;
if(!spi_start_enc(1)) return ERR_FAIL;
return ERR_OK;
}
static errcode_e spistat(_U_ cmd_e idx, _U_ char *par){
for(int i = 1; i < 3; ++i){
USB_sendstr(I_CMD, "SPI");
USB_putbyte(I_CMD, '0' + i);
USB_sendstr(I_CMD, ": ");
switch(spi_status[i]){
case SPI_NOTREADY:
USB_sendstr(I_CMD, "not ready");
break;
case SPI_BUSY:
USB_sendstr(I_CMD, "busy");
break;
case SPI_READY:
USB_sendstr(I_CMD, "ready");
break;
}
newline(I_CMD);
}
return ERR_SILENCE;
}
static errcode_e spiinit(_U_ cmd_e idx, _U_ char *par){
for(int i = 1; i < 3; ++i){
USB_sendstr(I_CMD, "Init SPI");
USB_putbyte(I_CMD, '0' + i);
newline(I_CMD);
spi_setup(i);
}
return ERR_SILENCE;
}
static errcode_e spideinit(_U_ cmd_e idx, _U_ char *par){
for(int i = 1; i < 3; ++i){
USB_sendstr(I_CMD, "DEinit SPI");
USB_putbyte(I_CMD, '0' + i);
newline(I_CMD);
spi_deinit(i);
}
return ERR_SILENCE;
}
// text commands
static const funcdescr_t commands[C_AMOUNT] = {
[C_dummy] = {"dummy", dummy},
[C_help] = {"help", help},
[C_sendX] = {"sendx", sendenc},
[C_sendY] = {"sendy", sendenc},
[C_setiface1] = {"setiface1", setiface},
[C_setiface2] = {"setiface2", setiface},
[C_setiface3] = {"setiface3", setiface},
[C_dumpconf] = {"dumpconf", dumpconf},
[C_erasestorage] = {"erasestorage", erasestor},
[C_storeconf] = {"storeconf", storeconf},
[C_reboot] = {"reboot", reboot},
[C_fin] = {"fin", fini},
[C_encstart] = {"readenc", encstart},
[C_spistat] = {"spistat", spistat},
[C_spiinit] = {"spiinit", spiinit},
[C_spideinit] = {"spideinit", spideinit},
};
static errcode_e dumpconf(cmd_e _U_ idx, char _U_ *par){
CMDWR("userconf_sz="); CMDWR(u2str(the_conf.userconf_sz));
CMDWR("\ncurrentconfidx="); CMDWR(i2str(currentconfidx));
CMDn();
for(int i = 0; i < bTotNumEndpoints; ++i)
setiface(C_setiface1 + i, NULL);
return ERR_SILENCE;
}
typedef struct{
int idx; // command index (if < 0 - just display `help` as grouping header)
const char *help; // help message
@ -113,18 +247,31 @@ typedef struct{
// SHOUL be sorted and grouped
static const help_t helpmessages[] = {
{-1, "Configuration"},
{C_dumpconf, "dump current configuration"},
{C_erasestorage, "erase full storage or current page (=n)"},
{C_setiface1, "set name of first (command) interface"},
{C_setiface2, "set name of second (axis X) interface"},
{C_setiface3, "set name of third (axis Y) interface"},
{C_storeconf, "store configuration in flash memory"},
{-1, "Different commands"},
{C_dummy, "dummy integer setter/getter"},
{C_encstart, "start reading encoders"},
{C_help, "show this help"},
{-1, "Debug commands"},
{C_sendX, "send text string to X encoder"},
{C_sendY, "send text string to Y encoder"},
{C_reboot, "reboot MCU"},
{C_spideinit, "deinit SPI"},
{C_spiinit, "init SPI"},
{C_spistat, "get status of both SPI interfaces"},
{-1, "Debug"},
{C_sendX, "send text string to X encoder's terminal"},
{C_sendY, "send text string to Y encoder's terminal"},
{C_fin, "reinit flash"},
{-1, NULL},
};
static errcode_e help(_U_ cmd_e idx, _U_ char* par){
CMDWRn("https://github.com/eddyem/stm32samples/tree/master/F1:F103/ build #" BUILD_NUMBER " @ " BUILD_DATE);
CMDWRn("commands format: command[=setter]\\n");
CMDWRn("https://github.com/eddyem/stm32samples/tree/master/F1:F103/BISS_C_encoders build #" BUILD_NUMBER " @ " BUILD_DATE);
CMDWRn("\ncommands format: 'command[=setter]\\n'");
const help_t *c = helpmessages;
while(c->help){
if(c->idx < 0){ // header
@ -139,7 +286,7 @@ static errcode_e help(_U_ cmd_e idx, _U_ char* par){
CMDn();
++c;
}
return ERR_OK;
return ERR_SILENCE;
}
// *cmd is "command" for commands/getters or "parameter=value" for setters
@ -149,7 +296,7 @@ void parse_cmd(char *cmd){
char *cmdstart = omit_spaces(cmd), *parstart = NULL;
if(!cmdstart) goto retn;
char *ptr = cmdstart;
while(*ptr > '@') ++ptr;
while(*ptr > ' ' && *ptr != '=') ++ptr;
if(*ptr && *ptr <= ' '){ // there's spaces after command
*ptr++ = 0;
ptr = omit_spaces(ptr);

View File

@ -0,0 +1,174 @@
/*
* This file is part of the encoders project.
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stm32f1.h>
#include "spi.h"
#include <string.h> // memcpy
#include "usb_dev.h"
#ifdef EBUG
#include "strfunc.h"
#endif
#define CHKIDX(idx) do{if(idx == 0 || idx > AMOUNT_OF_SPI) return;}while(0)
#define CHKIDXR(idx) do{if(idx == 0 || idx > AMOUNT_OF_SPI) return 0;}while(0)
spiStatus spi_status[AMOUNT_OF_SPI+1] = {0, SPI_NOTREADY, SPI_NOTREADY};
static volatile SPI_TypeDef* const SPIs[AMOUNT_OF_SPI+1] = {NULL, SPI1, SPI2};
static volatile DMA_Channel_TypeDef * const DMAs[AMOUNT_OF_SPI+1] = {NULL, DMA1_Channel2, DMA1_Channel4};
#define WAITX(x) do{volatile uint32_t wctr = 0; while((x) && (++wctr < 360000)) IWDG->KR = IWDG_REFRESH; if(wctr==360000){ DBG("timeout"); return 0;}}while(0)
static uint8_t encoderbuf[AMOUNT_OF_SPI][ENCODER_BUFSZ] = {0};
static uint8_t freshdata[AMOUNT_OF_SPI] = {0};
// init SPI to work RX-only with DMA
// SPI1 (PA5/PA6): DMA1_Channel2
// SPI2 (PB13/PB14): DMA1_Channel4
void spi_setup(uint8_t idx){
CHKIDX(idx);
volatile SPI_TypeDef *SPI = SPIs[idx];
SPI->CR1 = 0; // clear EN
SPI->CR2 = 0;
RCC->AHBENR |= RCC_AHBENR_DMA1EN;
volatile DMA_Channel_TypeDef *DMA = DMAs[idx];
if(idx == 1){ // PA5/PA6; 72MHz
RCC->APB2RSTR = RCC_APB2RSTR_SPI1RST;
RCC->APB2RSTR = 0; // clear reset
GPIOA->CRL = (GPIOA->CRL & ~(GPIO_CRL_CNF5 | GPIO_CRL_CNF6))
| CRL(5, CNF_AFPP|MODE_FAST) | CRL(6, CNF_FLINPUT);
RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
SPI->CR1 = SPI_CR1_BR_0 | SPI_CR1_BR_2; // Fpclk/64
NVIC_EnableIRQ(DMA1_Channel2_IRQn); // enable Rx interrupt
}else if(idx == 2){ // PB12..PB15; 36MHz
RCC->APB1RSTR = RCC_APB1RSTR_SPI2RST;
RCC->APB1RSTR = 0;
GPIOB->CRH = (GPIOB->CRH & ~(GPIO_CRH_CNF13 | GPIO_CRH_CNF14))
| CRH(13, CNF_AFPP|MODE_FAST) | CRH(14, CNF_FLINPUT);
RCC->APB1ENR |= RCC_APB1ENR_SPI2EN;
SPI->CR1 = SPI_CR1_BR_2; // Fpclk/32
NVIC_EnableIRQ(DMA1_Channel4_IRQn);
}else return; // err
// Baudrate = 0b110 - fpclk/128
SPI->CR1 |= SPI_CR1_MSTR | SPI_CR1_SSM | SPI_CR1_SSI | SPI_CR1_RXONLY;
SPI->CR2 = SPI_CR2_RXDMAEN;
DMA->CPAR = (uint32_t)&(SPI->DR);
DMA->CCR = DMA_CCR_MINC | DMA_CCR_TCIE | DMA_CCR_TEIE; // mem inc, hw->mem, Rx complete and error interrupt
spi_status[idx] = SPI_READY;
DBG("SPI works");
}
void spi_onoff(uint8_t idx, uint8_t on){
CHKIDX(idx);
volatile SPI_TypeDef *SPI = SPIs[idx];
if(on){
DBGs(u2str(idx));
DBG("turn on SPI");
SPI->CR1 |= SPI_CR1_SPE;
spi_status[idx] = SPI_BUSY;
}else{
SPI->CR1 &= ~SPI_CR1_SPE;
spi_status[idx] = SPI_READY;
}
}
// turn off given SPI channel and release GPIO
void spi_deinit(uint8_t idx){
CHKIDX(idx);
DBG("deinit SPI");
volatile SPI_TypeDef *SPI = SPIs[idx];
SPI->CR1 = 0;
SPI->CR2 = 0;
if(idx == 1){
RCC->APB2ENR &= ~RCC_APB2ENR_SPI1EN;
GPIOA->CRL &= ~(GPIO_CRL_CNF5 | GPIO_CRL_CNF6);
}else if(idx == 2){
RCC->APB1ENR &= ~RCC_APB1ENR_SPI2EN;
GPIOB->CRH &= ~(GPIO_CRH_CNF13 | GPIO_CRH_CNF14);
}
spi_status[idx] = SPI_NOTREADY;
}
int spi_waitbsy(uint8_t idx){
CHKIDXR(idx);
DBGs(u2str(idx));
DBG("wait busy");
WAITX(SPIs[idx]->SR & SPI_SR_BSY);
return 1;
}
// just copy last read encoder value into `buf`
// @return TRUE if got fresh data
int spi_read_enc(uint8_t encno, uint8_t buf[8]){
if(encno > 1 || !freshdata[encno]) return FALSE;
DBGs(u2str(encno)); DBG("Read encoder data");
memcpy(buf, encoderbuf[encno], ENCODER_BUFSZ);
freshdata[encno] = 0; // clear fresh status
return TRUE;
}
// start encoder reading over DMA
// @return FALSE if SPI is busy
// here `encodernum` is 0 (SPI1) or 1 (SPI2), not 1/2 as SPI index!
int spi_start_enc(int encodernum){
int spiidx = encodernum + 1;
DBG("start enc");
if(spiidx < 1 || spiidx > AMOUNT_OF_SPI) return FALSE;
if(spi_status[spiidx] != SPI_READY) return FALSE;
if(!spi_waitbsy(spiidx)) return FALSE;
if(SPI1->CR1 & SPI_CR1_SPE) DBG("spi1 works!");
if(SPI2->CR1 & SPI_CR1_SPE) DBG("spi2 works!");
volatile DMA_Channel_TypeDef *DMA = DMAs[spiidx];
DMA->CMAR = (uint32_t) encoderbuf[encodernum];
DMA->CNDTR = ENCODER_BUFSZ;
DBG("turn on spi");
spi_onoff(spiidx, 1);
DMA->CCR |= DMA_CCR_EN;
return TRUE;
}
// SSI got fresh data
void dma1_channel2_isr(){
if(DMA1->ISR & DMA_ISR_TEIF2){
DMA1->IFCR = DMA_IFCR_CTEIF2;
}
if(DMA1->ISR & DMA_ISR_TCIF2){
//uint32_t ctr = TIM2->CNT;
DMA1->IFCR = DMA_IFCR_CTCIF2;
freshdata[0] = 1;
//encoderbuf[5] = (ctr >> 16) & 0xff;
//encoderbuf[6] = (ctr >> 8 ) & 0xff;
//encoderbuf[7] = (ctr >> 0 ) & 0xff;
}
spi_onoff(1, 0);
// turn off DMA
DMA1_Channel2->CCR &= ~DMA_CCR_EN;
}
void dma1_channel4_isr(){
if(DMA1->ISR & DMA_ISR_TEIF4){
DMA1->IFCR = DMA_IFCR_CTEIF4;
}
if(DMA1->ISR & DMA_ISR_TCIF4){
DMA1->IFCR = DMA_IFCR_CTCIF4;
freshdata[1] = 1;
}
spi_onoff(2, 0);
// turn off DMA
DMA1_Channel4->CCR &= ~DMA_CCR_EN;
}

View File

@ -0,0 +1,22 @@
#pragma once
#include <stdint.h>
#define AMOUNT_OF_SPI (2)
#define ENCODER_BUFSZ (8)
typedef enum{
SPI_NOTREADY,
SPI_READY,
SPI_BUSY
} spiStatus;
extern spiStatus spi_status[AMOUNT_OF_SPI+1];
void spi_onoff(uint8_t idx, uint8_t on);
void spi_deinit(uint8_t idx);
void spi_setup(uint8_t idx);
int spi_waitbsy(uint8_t idx);
int spi_start_enc(int encodernum);
int spi_read_enc(uint8_t encno, uint8_t buf[8]);

View File

@ -18,14 +18,15 @@
#include <stm32f1.h>
#include <string.h>
#include "usb_dev.h"
/**
* @brief hexdump - dump hex array by 16 bytes in string
* @param sendfun - function to send data
* @param ifno - number of interface
* @param arr - array to dump
* @param len - length of `arr`
*/
void hexdump(int (*sendfun)(const char *s), uint8_t *arr, uint16_t len){
void hexdump(int ifno, 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){
@ -36,14 +37,14 @@ void hexdump(int (*sendfun)(const char *s), uint8_t *arr, uint16_t len){
if(l % 16 == 15){
*bptr++ = '\n';
*bptr = 0;
sendfun(buf);
USB_sendstr(ifno, buf);
bptr = buf;
}else *bptr++ = ' ';
}
if(bptr != buf){
*bptr++ = '\n';
*bptr = 0;
sendfun(buf);
USB_sendstr(ifno, buf);
}
}

View File

@ -20,7 +20,7 @@
#include <stdint.h>
void hexdump(int (*sendfun)(const char *s), uint8_t *arr, uint16_t len);
void hexdump(int ifno, uint8_t *arr, uint16_t len);
char *u2str(uint32_t val);
char *i2str(int32_t i);
char *uhex2str(uint32_t val);

View File

@ -15,6 +15,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include "flash.h"
#include "usb_descr.h"
#undef DBG
@ -166,18 +169,28 @@ _USB_LANG_ID_(LD, LANG_US);
_USB_STRING_(SD, u"0.0.1");
_USB_STRING_(MD, u"eddy@sao.ru");
_USB_STRING_(PD, u"USB BISS-C encoders controller");
_USB_STRING_(ID1, u"encoder_cmd");
_USB_STRING_(ID2, u"encoder_X");
_USB_STRING_(ID3, u"encoder_Y");
// iInterface will change on initialisation by config
#define _USB_IIDESCR_(str) {sizeof(str), 0x03, str}
typedef struct{
uint8_t bLength;
uint8_t bDescriptorType;
uint16_t bString[MAX_IINTERFACE_SZ];
}iidescr_t;
static iidescr_t iids[bTotNumEndpoints] = {
_USB_IIDESCR_(u"encoder_cmd"),
_USB_IIDESCR_(u"encoder_X"),
_USB_IIDESCR_(u"encoder_Y"),
};
static const void* const StringDescriptor[iDESCR_AMOUNT] = {
[iLANGUAGE_DESCR] = &LD,
[iMANUFACTURER_DESCR] = &MD,
[iPRODUCT_DESCR] = &PD,
[iSERIAL_DESCR] = &SD,
[iINTERFACE_DESCR1] = &ID1,
[iINTERFACE_DESCR2] = &ID2,
[iINTERFACE_DESCR3] = &ID3,
[iINTERFACE_DESCR1] = &iids[0],
[iINTERFACE_DESCR2] = &iids[1],
[iINTERFACE_DESCR3] = &iids[2],
};
static void wr0(const uint8_t *buf, uint16_t size, uint16_t askedsize){
@ -246,3 +259,14 @@ void get_descriptor(config_pack_t *pack){
break;
}
}
// change values of iInterface by content of global config
void setup_interfaces(){
for(int i = 0; i < bTotNumEndpoints; ++i){
if(the_conf.iIlengths[i]){
iids[i].bLength = the_conf.iIlengths[i];
memcpy(iids[i].bString, the_conf.iInterface[i], the_conf.iIlengths[i]);
}
iids[i].bDescriptorType = 0x03;
}
}

View File

@ -62,3 +62,4 @@ enum{
};
void get_descriptor(config_pack_t *pack);
void setup_interfaces();

View File

@ -401,6 +401,7 @@ void USB_setup(){
USB->BCDR |= USB_BCDR_DPPU;
NVIC_EnableIRQ(USB_IRQn);
#endif
setup_interfaces();
}

View File

@ -1,2 +1,2 @@
#define BUILD_NUMBER "19"
#define BUILD_DATE "2025-03-25"
#define BUILD_NUMBER "63"
#define BUILD_DATE "2025-03-26"