This commit is contained in:
Kizarm 2025-02-19 15:33:04 +01:00
parent f00b7884f7
commit c3d1d2706a
30 changed files with 2781 additions and 0 deletions

65
V203F6P6/midi/Makefile Normal file
View file

@ -0,0 +1,65 @@
# ch32v203
TARGET?= ch32v203
TOOL ?= gcc
#TOOL ?= clang
PRJ = example
VPATH = . ./$(TARGET)
BLD = ./build/
DFLAGS = -d
LFLAGS = -g
LDLIBS = -L./$(TARGET)/usbd -lusbd
BFLAGS = --strip-unneeded
CFLAGS = -MMD -Wall -ggdb -fno-exceptions -ffunction-sections -fdata-sections
CFLAGS+= -I. -I./common -I./$(TARGET) -I./$(TARGET)/usbd
DEL = rm -f
# zdrojaky
OBJS = main.o hack.o pwmclass.o
OBJS += tone.o midiplayer.o miditone.o
OBJS += spiblocked.o norflash.o
OBJS += intelhex.o linkprotocol.o usb_desc.o
include $(TARGET)/$(TOOL).mk
BOBJS = $(addprefix $(BLD),$(OBJS))
all: $(BLD) $(PRJ).elf
# ... atd.
-include $(BLD)*.d
# linker
$(PRJ).elf: $(BOBJS)
-@echo [LD $(TOOL),$(TARGET)] $@
@$(LD) $(LFLAGS) -o $(PRJ).elf $(BOBJS) $(LDLIBS)
-@echo "size:"
@$(SIZE) $(PRJ).elf
-@echo "listing:"
$(DUMP) $(DFLAGS) $(PRJ).elf > $(PRJ).lst
-@echo "OK."
$(COPY) $(BFLAGS) -O binary $(PRJ).elf $(PRJ).bin
$(COPY) $(BFLAGS) -O ihex $(PRJ).elf $(PRJ).hex
# preloz co je potreba
$(BLD)%.o: %.c
-@echo [CC $(TOOL),$(TARGET)] $@
@$(CC) -c $(CFLAGS) $< -o $@
$(BLD)%.o: %.cpp
-@echo [CX $(TOOL),$(TARGET)] $@
@$(CXX) -std=c++17 -fno-rtti -c $(CFLAGS) $< -o $@
$(BLD):
mkdir $(BLD)
flash: $(PRJ).elf
minichlink -w $(PRJ).bin flash -b
miditone.c: ton/gen
ton/gen
ton/gen: ton/gen.cpp
g++ -Os ton/gen.cpp -o ton/gen
# vycisti
clean:
$(DEL) $(BLD)* *.lst *.bin *.hex *.elf *.map *~ miditone.c
distclean: clean
$(DEL) ton/gen
cd ./img && make distclean
.PHONY: all clean distclean flash

16
V203F6P6/midi/audio.h Normal file
View file

@ -0,0 +1,16 @@
#ifndef AUDIO_H
#define AUDIO_H
#include <stdint.h>
static constexpr int AudioSampleRate = 24000;
/// Počet generátorů.
static constexpr unsigned int maxGens = 12;
/// Kladné maximum vzorku.
static constexpr int maxValue = 30000;
/// Záporné maximum vzorku.
static constexpr int minValue = -maxValue;
///
static constexpr unsigned int maxAmplt = (1U<<27);
#endif // AUDIO_H

39
V203F6P6/midi/cache.h Normal file
View file

@ -0,0 +1,39 @@
#ifndef _CACHE_H_DEF
#define _CACHE_H_DEF
#include "norflash.h"
template<typename T, const unsigned N> class Cache {
T data [N];
unsigned begin_address;
unsigned ofset_current;
NorFlash nor;
public:
explicit Cache () noexcept : begin_address(0u), ofset_current(0u), nor() {}
Cache & operator= (const unsigned addr) {
begin_address = addr;
ofset_current = 0u;
reload ();
return * this;
}
operator bool () { if (begin_address == 0u) return false; return true; }
T * operator++ (int) {
check_validity ();
T * tmp = data + ofset_current;
ofset_current += 1;
return tmp;
}
protected:
void check_validity () {
if (ofset_current >= N) {
begin_address += N * sizeof(T);
reload ();
ofset_current = 0u;
}
}
void reload () {
unsigned char * ptr = reinterpret_cast<unsigned char *>(data);
nor.ReadBlock (begin_address, ptr, N * sizeof(T));
}
};
#endif // _CACHE_H_DEF

1
V203F6P6/midi/ch32v203 Symbolic link
View file

@ -0,0 +1 @@
../ch32v203/

1
V203F6P6/midi/common Symbolic link
View file

@ -0,0 +1 @@
../common/

112
V203F6P6/midi/endien.h Normal file
View file

@ -0,0 +1,112 @@
#ifndef ENDIEN_H
#define ENDIEN_H
#include <stdint.h>
/**
@file
@brief Pořadí bytů ve slově.
*/
#if __GNUC__ < 4 // Compiler test
#error "Zkontroluj nastaveni"
#endif
// Pro 16.bit musíme struktury pakovat
#define PACKSTRUCT __attribute__((packed))
#if __thumb__
// Specificky a krátce pro danou architektutu a pokud chceme mít úplnou kontrolu.
static inline uint32_t swab32 (uint32_t p) {
register uint32_t t;
asm volatile ("rev %0, %1":"=r"(t):"0"(p));
return t;
}
#else //__thumb__
// Obecně
static inline uint32_t swab32 (uint32_t p) {
// return __builtin_bswap32 (p); // I takto to jde, ale pro názornost:
return ((p & 0x000000ff) << 24)
| ((p & 0x0000ff00) << 8)
| ((p & 0x00ff0000) >> 8)
| ((p & 0xff000000) >> 24);
}
#endif //__thumb__
// Tohle nebudeme ani specifikovat pro ARM
static inline uint16_t swab16 (uint16_t p) {
return (p << 8) | (p >> 8);
}
// Nedělá nic a ani nijak neotravuje - pouze pro kompatibilitu
static inline uint32_t nosw32 (uint32_t p) {
return p;
}
static inline uint16_t nosw16 (uint16_t p) {
return p;
}
// Specificky podle pořadí bytů - LE je pro ARM i X86 normální.
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define cpu_to_be32 swab32
#define be32_to_cpu swab32
#define cpu_to_le32 nosw32
#define le32_to_cpu nosw32
#define cpu_to_be16 swab16
#define be16_to_cpu swab16
#define cpu_to_le16 nosw16
#define le16_to_cpu nosw16
#else
#define cpu_to_be32 nosw32
#define be32_to_cpu nosw32
#define cpu_to_le32 swab32
#define le32_to_cpu swab32
#define cpu_to_be16 nosw16
#define be16_to_cpu nosw16
#define cpu_to_le16 swab16
#define le16_to_cpu swab16
#endif
/**
@class ui32be
@brief C++ 32.bit data v BIG Endien.
@class ui32le
@brief C++ 32.bit data v LITTLE Endien.
@class ui16be
@brief C++ 16.bit data v BIG Endien.
@class ui16le
@brief C++ 16.bit data v LITTLE Endien.
*/
class ui32be {
public:
/// Nastavení hodnoty
/// @par p uint32_t hodnota
void set (uint32_t p) {num = cpu_to_be32 (p); };
/// Zjištění hodnoty
/// @return uint32_t hodnota
uint32_t get (void) {return be32_to_cpu (num); };
private:
/// Vlastní data uint32_t - k nim se přímo nedostaneme, jsou privátní
uint32_t num;
}PACKSTRUCT;
class ui32le {
public:
void set (uint32_t p) {num = cpu_to_le32 (p); };
uint32_t get (void) {return le32_to_cpu (num); };
private:
uint32_t num;
}PACKSTRUCT;
/* *******************************************************/
class ui16be {
public:
void set (uint16_t p) {num = cpu_to_be16 (p); };
uint16_t get (void) {return be16_to_cpu (num); };
private:
uint16_t num;
}PACKSTRUCT;
class ui16le {
public:
void set (uint16_t p) {num = cpu_to_le16 (p); };
uint16_t get (void) {return le16_to_cpu (num); };
private:
uint16_t num;
}PACKSTRUCT;
#endif // ENDIEN_H

21
V203F6P6/midi/hack.c Normal file
View file

@ -0,0 +1,21 @@
#include <stdint.h>
#include <stdarg.h>
typedef __SIZE_TYPE__ size_t;
size_t strlen (const char *s) {
size_t l = 0;
while (*s++) l++;
return l;
}
void *memcpy (void *dest, const void *src, size_t n) {
const char *s = (const char *) src;
char *d = (char *) dest;
int i;
for (i=0; i<n; i++) d[i] = s[i];
return dest;
}
void *memset (void *s, int c, size_t n) {
char *p = (char *) s;
int i;
for (i=0; i<n; i++) p[i] = c;
return s;
}

61
V203F6P6/midi/helpers.h Normal file
View file

@ -0,0 +1,61 @@
#ifndef HELPERS_H
#define HELPERS_H
#include <stdint.h>
#include "norflash.h"
#include "fifo.h"
#include "baselayer.h"
class MemoryBase {
NorFlash flash;
public:
explicit MemoryBase () noexcept : flash() {}
uint32_t Write (const uint32_t addr, const void * ptr, const uint32_t len) {
return flash.WriteBlock(addr, reinterpret_cast<const uint8_t*>(ptr), len);
}
uint32_t Read (const uint32_t addr, void * ptr, const uint32_t len) {
return flash.ReadBlock(addr, reinterpret_cast<uint8_t*> (ptr), len);
}
void Erase (const uint32_t blok) {
/* Flash používá 24-bitovou adresu počátku bloku, zde máme k dispozici
* jen 16-bitový offset. Takže to jen natáhneme 256 krát. Software
* tomu musí odpovídat.
* */
flash.EraseSector(blok << 8);
}
};
class CdcCmd : public BaseLayer {
static constexpr int maxlen = 0x80;
FIFO<char, maxlen> ring;
char line [maxlen];
unsigned index;
public:
explicit CdcCmd () noexcept : BaseLayer(), ring(), index(0u) {}
uint32_t Up(const char * data, const uint32_t len) override {
for (unsigned n=0u; n<len; n++) ring.Write (data [n]);
return len;
}
void SendString (const char * ptr, const uint32_t len) {
unsigned ofs = 0u, res = len;
while (res) {
const unsigned chunk = res > 0x20u ? 0x20u : res;
const unsigned n = Down (ptr + ofs, chunk);
ofs += n;
res -= n;
}
}
char * GetLine (unsigned & len) {
char c;
while (ring.Read(c)) {
line [index++] = c;
if (c == '\n') {
len = index;
index = 0u;
return line;
}
}
len = 0u;
return line;
}
};
#endif // HELPERS_H

View file

@ -0,0 +1,31 @@
PR = image
VPATH = .
CXX = g++
CC = gcc
CFLAGS = -Wall -Os -I..
MFLAGS = -o $(PR)
LFLAGS =
all: data.bin $(PR)
OBJECTS = main.o
# fakticky není potřeba
$(PR): $(OBJECTS)
$(CXX) $(MFLAGS) $(OBJECTS) $(LFLAGS)
clean:
rm -f *.o *~ data.elf data.map $(PR) extdata.c
distclean: clean
rm -f data.bin miditones melody.c
%.o: %.cpp
$(CXX) -std=c++14 -c $(CFLAGS) -o $@ $<
%.o: %.c
$(CC) -c $(CFLAGS) -o $@ $<
data.elf: melody.c
riscv64-unknown-elf-gcc -march=rv32imac -mabi=ilp32 -Wall -Os -I.. -fdata-sections -Wl,-Map=data.map,--gc-sections -nostdlib -nostartfiles -o data.elf melody.c -L. -T script.ld
data.bin: data.elf
riscv64-unknown-elf-objcopy -O binary data.elf data.bin
melody.c: miditones
./miditones -d -s2 -t12 ../mid/
miditones: miditonesV1.6.c
gcc -Os -Wno-pointer-sign -Wno-return-type miditonesV1.6.c -o miditones
.PHONY: all clean

View file

@ -0,0 +1,69 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdint.h>
#include <stdio.h>
/* Jedna z možností jak dostat data do externí flash. Sice to poněkud
* nabobtná (na začátku je zbytečně celá struktura SayedTexts + padding),
* ale funguje to.
* 1. zdroják melofy.c se přeloží a pomocí linker skriptu se vytvoří elf
* 2. z elf se udělá binárka, příp. hex soubor standardním binutils
* 3. binárka se tímto přečte a do extdata.c se extrahují potřebné informace.
* extdata.c pak obsahuje adresy meloddií potřebné pro čtení.
* Nezávisí to na tom, zda se binárka vytváří na 64. nebo 32. bit stroji. */
class Reader {
unsigned char * file_data;
int file_size;
public:
explicit Reader() : file_data(nullptr), file_size(0) {}
~Reader() {
if (file_data) delete [] file_data;
}
int process (const char * filename);
protected:
void check ();
void generate();
};
int Reader::process(const char * filename) {
struct stat statbuf;
int r = ::stat(filename, &statbuf);
if (r < 0) return -1;
if (file_data) delete [] file_data;
file_data = new unsigned char [statbuf.st_size];
FILE * in_file = fopen(filename, "r");
r = fread (file_data, 1, statbuf.st_size, in_file);
file_size = r;
printf("readen %d bytes (%g 0x1000 blocks)\n", file_size, double(file_size)/4096.0);
if (!in_file) return -1;
fclose(in_file);
check(); // kontrola - mělo by sedět to co je v data.bin a přeložené melody.c
generate(); // generuje C-soubor s adresami a počty rámců
return 0;
}
void Reader::check() {
if (!file_size) return;
}
// Jen toto je podstatné - vytvoření extdata.c
static const char * prefix = R"---(/* GENERATED FILE DO NOT EDIT */
const unsigned scores [] = {)---";
void Reader::generate() {
FILE * out = fopen("extdata.c","w");
fprintf(out, "%s", prefix);
uint32_t * ptr = reinterpret_cast<uint32_t*>(file_data);
for (unsigned n=0; ; n++) {
if ((n%8) == 0) fprintf(out, "\n");
const size_t e = ptr[n];
fprintf(out, " 0x%06lXu,", e);
if (e == 0lu) break;
}
fprintf(out, "\n};\n");
fclose(out);
}
/// main ()
int main (int argc, char *argv[]) {
Reader read;
return read.process("data.bin");
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,40 @@
/*OUTPUT_FORMAT (binary) možnosti zjistíme příkazem "objdump -i" (lze i ihex) */
ENTRY (scores) /* odtud se začne (start po resetu) */
/* popis pamětí */
MEMORY {
ram (RWX) : ORIGIN = 0x00, LENGTH = 0x40000
}
/* popis sekcí */
SECTIONS {
.fixed : { /* výstupní sekce v rom - nazveme např. .fixed */
. = ALIGN (8); /* zarovnání příští sekce na 4 byty (celkem zbytečné) */
KEEP(*(.rodata.scores));
*(.rodata*) /* .rodata* - stejně jako .text*, ale pro const proměnné (data) (-fdata-sections) */
. = ALIGN (8);
*(.text*) /* gcc pojmenovává sekce s kódem jako .text, pokud je použito -ffunction-sections,
pak bude každá funkce v jiné sekci s názvy .text.jmeno_funkce, proto ta * na konci */
. = ALIGN (8);
*(.data*) /* .data* - stejně jako .text*, ale pro proměnné (data inicializovaná) */
. = ALIGN (8);
*(.bss*) /* .bss* - stejně jako .text*, ale pro proměnné (data inicializovaná na 0) */
*(COMMON*)
*(.note.*)
. = ALIGN (4096);
relocate_end = .; /* konec dat v sekci .relocate - podle toho se např. nějak určí hodnota SP */
end = .;
} > ram /* tady je řečeno, že to má být v ram */
/DISCARD/ : {
*(.rela*)
*(.dynamic)
*(.eh*)
*(.debug*)
*(.comment*)
*(.interp)
*(.dynsym)
*(.dynstr)
*(.hash)
*(.gnu.hash)
*(.header)
} : phdr
}

251
V203F6P6/midi/intelhex.cpp Normal file
View file

@ -0,0 +1,251 @@
#include "endien.h"
#include "intelhex.h"
typedef __SIZE_TYPE__ size_t;
extern "C" void *memcpy (void *dest, const void *src, size_t n);
extern "C" size_t strlen (const char *s);
static uint8_t hexval (const char c) {
if ('0' <= c && c <= '9')
return (uint8_t) c - '0';
else if ('a' <= c && c <= 'f')
return (uint8_t) c - 'a' + 10;
else return 0;
}
static unsigned to_hex_str (char * str, const uint8_t * data, const unsigned len) {
unsigned result = 0;
const char * hexstr = "0123456789ABCDEF";
for (unsigned n=0; n<len; n++) {
const uint8_t c = data [n];
str [result++] = hexstr [c >> 4];
str [result++] = hexstr [c & 0xF];
}
return result;
}
static uint8_t chk_sum (const uint8_t * data, const unsigned len) {
uint8_t sum = 0;
for (unsigned n=0; n<len; n++) sum += data [n];
return sum;
}
union IhxHeader {
struct {
uint8_t reclen;
ui16be load;
RowTypes rectyp;
}__attribute__((packed));
uint8_t bytes [4];
};
union UlbaAddr {
ui16be addr;
uint8_t bytes [2];
};
union EipAddr {
ui32be addr;
uint8_t bytes [4];
};
IntelHex::IntelHex() noexcept : address(0), offset(0), lenght(0), total_lenght(0), ackofs(0) {
}
RowTypes IntelHex::parseRecord (const char * data, const unsigned int len) {
//printf ("parseRecord: %d %s\r\n", len, data);
if (data[0] != (uint8_t) ':') return badRecord;
uint8_t tmp [(len >> 1) + 1];
uint8_t val = 0;
unsigned k = 0;
for (unsigned n=1; n<len; n++) {
const uint8_t c = data [n];
if (c == '\r') break;
if (c == '\n') break;
val *= 0x10;
val += hexval (c | 0x20);
if ((n & 1) == 0) {
tmp [k++] = val;
val = 0;
}
}
k -= 1;
uint8_t chk = 0;
for (unsigned n=0; n<k; n++) chk += tmp [n];
chk = 0 - chk;
// kontrolní součet
if (chk != tmp[k]) return badRecord;
// chybná délka
if (k < sizeof (IhxHeader)) return badRecord;
IhxHeader hdr;
memcpy (hdr.bytes, tmp, sizeof(IhxHeader));
// chybná délka
if (k != (hdr.reclen + sizeof(IhxHeader))) return badRecord;
//printf ("%d: %d=%d, %d, %02X=%02X\n", len, k, hdr.reclen+4, hdr.rectyp, chk, tmp[k]);
UlbaAddr a1; EipAddr a2;
RowTypes t = hdr.rectyp;
lenght = hdr.reclen;
const uint8_t * start = tmp + sizeof(IhxHeader);
uint32_t tmpadr;
// uint16_t old_ofs = offset;
switch (t) {
case dataRecord:
memcpy (chunk, start, lenght);
offset = hdr.load.get();
// if (old_ofs != offset) printf ("Posun 0x%04X-0x%04X\n", old_ofs, offset);
total_lenght = offset + lenght;
break;
case elaRecord:
total_lenght = 0;
memcpy (a1.bytes, start, lenght);
address = (uint32_t) a1.addr.get() << 16;
offset = hdr.load.get();
break;
case slaRecord:
memcpy (a2.bytes, start, lenght);
address = a2.addr.get();
offset = hdr.load.get();
break;
case ackRecord:
case nakRecord:
ackofs = hdr.load.get();
break;
case eofRecord:
case reqRecord:
case ersRecord:
offset = hdr.load.get();
break;
// Tyhle 2 rekordy nějak fungují, ale je to divné. Bohužel třeba objcopy
// je na nízkých adresách z nějakého důvodu používá. Možná je v tom chyba.
case esaRecord:
total_lenght = 0;
memcpy (a1.bytes, start, lenght);
address = (uint32_t) a1.addr.get() << 4;
offset = hdr.load.get();
break;
case ssaRecord:
memcpy (a2.bytes, start, lenght);
tmpadr = a2.addr.get();
address = (tmpadr >> 12); // CS:IP ? čerti vědí, jak je to myšleno
offset = hdr.load.get();
break;
default:
break;
}
return t;
}
bool IntelHex::CompareAckOffsset() {
//printf ("ofs: %04X=%04X\n", ackofs, offset);
if (ackofs == offset) return true;
return false;
}
void IntelHex::getAddress (uint32_t & adr) {
adr = address;
}
void IntelHex::getLenght (uint32_t & len) {
len = total_lenght;
}
void IntelHex::getOffset (uint32_t & ofs) {
ofs = offset;
}
void IntelHex::WriteData (uint8_t * data) {
memcpy (data + offset, chunk, lenght);
offset += lenght;
}
uint8_t * IntelHex::getDataRow (uint32_t & ofs, uint32_t & len) {
ofs = offset;
len = lenght;
return chunk;
}
void IntelHex::AddOffset (uint32_t len) {
offset += len;
}
uint32_t IntelHex::ElaRecord (char * string, const uint32_t adr) {
offset = 0;
uint32_t result = 0;
string [result++] = ':';
UlbaAddr lba;
IhxHeader hdr;
hdr.load.set(0);
hdr.reclen = sizeof (lba);
hdr.rectyp = elaRecord;
lba.addr.set (adr >> 16);
uint8_t chk = 0;
chk += chk_sum (hdr.bytes, sizeof(IhxHeader));
chk += chk_sum (lba.bytes, sizeof(UlbaAddr ));
chk = 0 - chk;
result += to_hex_str (string + result, hdr.bytes, sizeof(IhxHeader));
result += to_hex_str (string + result, lba.bytes, sizeof(UlbaAddr ));
result += to_hex_str (string + result, & chk, 1);
string [result++] = '\r';
string [result++] = '\n';
string [result] = '\0';
return result;
}
uint32_t IntelHex::SlaRecord (char* string, const uint32_t adr) {
offset = 0;
uint32_t result = 0;
string [result++] = ':';
EipAddr lba;
IhxHeader hdr;
hdr.load.set(0);
hdr.reclen = sizeof (lba);
hdr.rectyp = slaRecord;
lba.addr.set (adr);
uint8_t chk = 0;
chk += chk_sum (hdr.bytes, sizeof(IhxHeader));
chk += chk_sum (lba.bytes, sizeof(EipAddr ));
chk = 0 - chk;
result += to_hex_str (string + result, hdr.bytes, sizeof(IhxHeader));
result += to_hex_str (string + result, lba.bytes, sizeof(EipAddr ));
result += to_hex_str (string + result, & chk, 1);
string [result++] = '\r';
string [result++] = '\n';
string [result] = '\0';
return result;
}
uint32_t IntelHex::DataRecord (char * string, const uint8_t * data, const uint32_t len) {
uint32_t result = 0;
string [result++] = ':';
IhxHeader hdr;
hdr.load.set (offset);
hdr.reclen = len;
hdr.rectyp = dataRecord;
uint8_t chk = 0;
chk += chk_sum (hdr.bytes, sizeof(IhxHeader));
chk += chk_sum (data, len);
chk = 0 - chk;
result += to_hex_str (string + result, hdr.bytes, sizeof(IhxHeader));
if (len) result += to_hex_str (string + result, data, len);
result += to_hex_str (string + result, & chk, 1);
string [result++] = '\r';
string [result++] = '\n';
string [result] = '\0';
return result;
}
uint32_t IntelHex::EofRecord (char * string) {
offset = 0;
const char * eof = ":00000001FF\r\n";
const uint32_t result = strlen (eof);
memcpy (string, eof, result);
string [result] = '\0';
return result;
}
uint32_t IntelHex::BTxRecord (char * string, RowTypes t) {
uint32_t result = 0;
string [result++] = ':';
IhxHeader hdr;
hdr.load.set (offset);
hdr.reclen = 0;
hdr.rectyp = t;
uint8_t chk = 0;
chk += chk_sum (hdr.bytes, sizeof(IhxHeader));
chk = 0 - chk;
result += to_hex_str (string + result, hdr.bytes, sizeof(IhxHeader));
result += to_hex_str (string + result, & chk, 1);
string [result++] = '\r';
string [result++] = '\n';
string [result] = '\0';
//offset += len;
return result;
}

138
V203F6P6/midi/intelhex.h Normal file
View file

@ -0,0 +1,138 @@
#ifndef INTELHEX_H
#define INTELHEX_H
#include <stdint.h>
/// Maximální délka rekordu
static const unsigned MaxHexRecord = 0x80u;
enum RowTypes : uint8_t {
// defined by Intel
dataRecord = 0, //!< datový paket
eofRecord, //!< konec dat
esaRecord, //!< X86/16.bit CS/IP počáteční adresa
ssaRecord, //!< X86/16.bit CS/IP startovací adresa
elaRecord, //!< počáteční adresa 32-bit (horních 16.bit)
slaRecord, //!< startovací adresa 32-bit
// ... defined by BT protocol
// všechny budou používat jen offset pro přenos dat.
ackRecord, //!< offset - představuje offset předchozích dat
nakRecord, //!< stejně jako předchozí
reqRecord, //!< Request for data - offset značí odkud vzhledem k počáteční adrese
ersRecord, //!< Erase - po blocích, offset je číslo bloku (závisí na typu flash)
badRecord, //!< použito pro detekci chyb
};
/// @enum RowTypes
/// Enumerace datových typů Intel-hex formátu
/**
* @class IntelHex
* @brief Hepler pro parsování a vytváření paketů Intel HEX formátu.
*
* Prvních 6 typů (#RowTypes) je definováno ve specifikaci např.
* <a href="https://cs.wikipedia.org/wiki/Intel_HEX">zde</a>. A protože je tam dost volného
* místa pro rozšíření, jsou zde dodefinovány další typy použité pro komunikaci. Je to tak
* jednodušší než vymýšlet nějaký vlastní formát pro potvrzování a jiné. Protokol je znakový,
* poměrně robustní, na druhou stranu dost velku režii.
*
* Ukázalo sem že úzkým hrdlem komunikace je čekání na potvrzování, takže se režie poněkud zastírá,
* ale potvrzování být musí pokud to trochu spolehlivě fungovat.
* */
class IntelHex {
public:
explicit IntelHex() noexcept;
/**
* @brief Základní funkce parseru
*
* @param data vstupní řádek (paket)
* @param len jeho délka (např. strlen(data))
* @return RowTypes - to se pak zpracovává dál v nadřazené části např. LinkProtocol
*/
RowTypes parseRecord (const char * data, const unsigned len);
/**
* @brief Nastavení adresy
*
* Buď počáteční nebo starovací.
*
* @param adr odkaz na proměnnou, kam bude adresa nastavena
*/
void getAddress (uint32_t & adr);
/**
* @brief Nastavení délky
* @param len odkaz na proměnnou, kam bude délka nastavena
*/
void getLenght (uint32_t & len);
/**
* @brief Nastavení ofsetu
* @param ofs odkaz na proměnnou, kam bude ofset nastaven
*/
void getOffset (uint32_t & ofs);
/**
* @brief Zápis dat z příchozího dataRecord
*
* @param data kam se zapíší
*/
void WriteData (uint8_t * data);
/**
* @brief Posun ofsetu kvůli udržení konzistence
*
* @param len o kolik se posunout
*/
void AddOffset (uint32_t len);
/**
* @brief Vytvoření dataRecord
*
* @param string kam se record zapíše
* @param data pure data rekordu
* @param len a jejich délka
* @return uint32_t kolik bylo zapsáno do string
*/
uint32_t DataRecord (char * string, const uint8_t * data, const uint32_t len);
/**
* @brief Vytvoření elaRecord
*
* @param string kam se record zapíše
* @param adr počáteční adresa
* @return uint32_t kolik znaků bylo zapsáno do string
*/
uint32_t ElaRecord (char * string, const uint32_t adr);
/**
* @brief Vytvoření slaRecord
*
* @param string kam se record zapíše
* @param adr startovací adresa
* @return uint32_t kolik znaků bylo zapsáno do string
*/
uint32_t SlaRecord (char * string, const uint32_t adr);
/**
* @brief Vytvoření eofRecord
*
* @param string kam se record zapíše
* @return uint32_t kolik znaků bylo zapsáno do string
*/
uint32_t EofRecord (char * string);
/**
* @brief Vytvoření BT rekordů ackRecord, nakRecord, reqRecord, ersRecord
*
* @param string kam se record zapíše
* @param t definuj typ rekordu
* @return uint32_t kolik znaků bylo zapsáno do string
*/
uint32_t BTxRecord (char * string, RowTypes t = ackRecord);
/// pomocná funkce
bool CompareAckOffsset ();
/// pomocná funkce
uint8_t * getDataRow (uint32_t & ofs, uint32_t & len);
protected:
private:
uint32_t address;
uint32_t offset;
uint32_t lenght;
uint32_t total_lenght;
uint32_t ackofs;
uint8_t chunk [MaxHexRecord];
};
#endif // INTELHEX_H

View file

@ -0,0 +1,76 @@
#include "helpers.h"
#include "linkprotocol.h"
#define trace(...)
typedef __SIZE_TYPE__ size_t;
extern "C" size_t strlen (const char *s);
LinkProtocol::LinkProtocol(CdcCmd & g, MemoryBase & b) noexcept : cdccmd(g), ihx(), driver(b),
begin_addr(0), start_addr(0), lenght (0) {
}
void LinkProtocol::ParseLine (const char * line) {
const unsigned readen = strlen (line);
RowTypes t = ihx.parseRecord (line, readen);
switch (t) {
case dataRecord: AcquireDataRecord (); break;
case elaRecord: AcquireElaRecord (); break;
case slaRecord: AcquireSlaRecord (); break;
case eofRecord: AcquireEofRecord (); break;
case reqRecord: AcquireReqRecord (); break;
case ersRecord: AcquireErsRecord (); break;
default:
trace ("BAD record 0x%02X\r\n", t);
break;
}
}
void LinkProtocol::AcquireDataRecord() {
uint32_t ofs, len;
uint8_t * ptr = ihx.getDataRow (ofs, len);
uint32_t res = driver.Write (begin_addr + ofs, ptr, len);
if (res) {
SendAck();
ihx.AddOffset (res); // posun offsetu az po ack
return;
}
SendAck (false);
}
// První paket, který musí přijít při každé akci.
void LinkProtocol::AcquireElaRecord() {
ihx.getAddress (begin_addr);
trace("Begin = 0x%08X\r\n", begin_addr);
SendAck();
}
typedef void (*pHandler) (void);
void LinkProtocol::AcquireSlaRecord() {
ihx.getAddress (start_addr);
trace("Start = 0x%08X\r\n", start_addr);
}
void LinkProtocol::AcquireEofRecord() {
ihx.getLenght (lenght);
trace ("Lenght = %d\r\n", lenght);
SendAck();
}
void LinkProtocol::AcquireReqRecord() {
static constexpr unsigned chunk_size = 0x20;
uint8_t data [chunk_size];
uint32_t res, len, ofs;
len = chunk_size;
ihx.getOffset (ofs);
//trace ("AcquireReqRecord: %04X, len=%d\r\n", ofs, len);
res = driver.Read (begin_addr + ofs, data, len);
res = ihx.DataRecord(strbuf, data, res);
cdccmd.SendString (strbuf, res);
}
void LinkProtocol::AcquireErsRecord() {
uint32_t block;
ihx.getOffset (block);
driver.Erase (block);
SendAck();
}
void LinkProtocol::SendAck (bool ok) {
RowTypes t = ok ? ackRecord : nakRecord;
const uint32_t r = ihx.BTxRecord (strbuf, t);
cdccmd.SendString (strbuf, r);
}

View file

@ -0,0 +1,55 @@
#ifndef LINKPROTOCOL_H
#define LINKPROTOCOL_H
#include <stdint.h>
#include "intelhex.h"
class CdcCmd;
class MemoryBase;
/**
* @class LinkProtocol
* @brief Protokol po CDC.
*
* Vychází z formátu IntelHex. komunikaci začíná PC, modul odpovídá. První co musí PC (vždy na začátku konunikace !) poslat
* je paket ElaRecord (4), který určuje počáteční adresu - podle modul vybere potřebný ovladač paměti. Formy komunikace :
* -# Mazání obsahu flash. Probíhá po jednotlivých blocích.
* - PC -> MODUL : ersRecord (v poli offset číslo bloku relativně k počátku typu paměti)
* - MODUL -> PC : ackRecord (v poli offset číslo bloku relativně k počátku typu paměti)
* -# Zápis dat do paměti.
* - PC -> MODUL : dataRecord (max. 32 bytů)
* - MODUL -> PC : ackRecord nebo nakRecord pokud je cosi špatně
* -# Čtení dat z paměti.
* - PC -> MODUL : reqRecord (v poli offset odkud chci číst)
* - MODUL -> PC : dataRecord - pokud nulovou délku, další data nejsou. Posílána je délka 32 bytů.
* -# Spuštění uživatelského programu.
* - PC -> MODUL : slaRecord
* - bez odpovědi - je to jen pro uživatelské testy a může to špatně skončit. Vlastně je to přidáno
* jen proto, že to jde.
* */
class LinkProtocol {
static constexpr unsigned strbuflen = 0x80u;
public:
explicit LinkProtocol(CdcCmd & g, MemoryBase & b) noexcept;
void ParseLine (const char * line);
protected:
void AcquireDataRecord ();
void AcquireElaRecord ();
void AcquireSlaRecord ();
void AcquireEofRecord ();
void AcquireReqRecord ();
void AcquireErsRecord ();
void SendAck (bool ok=true);
private:
CdcCmd & cdccmd;
IntelHex ihx;
MemoryBase & driver;
uint32_t begin_addr;
uint32_t start_addr;
uint32_t lenght;
char strbuf [strbuflen];
};
#endif // LINKPROTOCOL_H

65
V203F6P6/midi/main.cpp Normal file
View file

@ -0,0 +1,65 @@
#include "system.h"
#include "midiplayer.h"
#include "cdc_class.h"
#include "linkprotocol.h"
#include "helpers.h"
/*
* Player na tomto čipu 12 generátorů, lepší
* rozlišení PWM, tedy lepší zvuk.
* Zase menší paměť, takže stálo za zkoušku dát
* data melodií do externí SPI flash. Číst to z
* po bytu by asi bylo dost neefektivní, takže je tam
* vložena třída Cache. Zároveň to ukazuje jak efektivní
* je použití C++ proti čistému C. Zatímco v C-čku by to
* asi byl velký zásah do původního kódu, zde stačí
* ve třídě Cache přetížit 3 operátory a do původního
* kódu se nemusí takřka zasahovat.
* Binární obraz melodií pro externí flash se vytvoří
* pomocí Makefile v adresáří img, je nezávislý na
* firmware procesoru. Melodie to bere z adresáře mid,
* pořadí je víceméně náhodné.
*
* Vzhledem k tomu, že je tam místa dost, vejde se tam
* i programátor nor flash, takže není potřeba přepínat
* firmware, stačí připojit do USB a pokud se to do 2s
* enumeruje, můžeme programovat.
*/
extern "C" volatile uint32_t bDeviceState;
GpioClass led (GPIOB,8u);
// player
static MidiPlayer player (led);
static PwmClass pcm;
// programátor
static cdc_class cdc;
static MemoryBase mem;
static CdcCmd cmd;
static LinkProtocol lnk (cmd, mem);
static void player_loop () {
for (;;) {
player.pass();
}
}
static void programmer_loop () {
led << true;
unsigned command = 0u;
for (;;) {
char * res = cmd.GetLine(command);
if (command == 0u) continue;
lnk.ParseLine(res);
}
}
int main (void) {
delay_init ();
cdc.init ();
Delay_Ms (2000); // čekat se musí poměrně dlouho
if (bDeviceState > 1u) { // enumerováno, můžeme programovat
cmd += cdc;
programmer_loop();
} else { // jinak můžeme hrát
pcm.attach (player);
player_loop ();
}
return 0;
}

1
V203F6P6/midi/mid Symbolic link
View file

@ -0,0 +1 @@
../../V203/midi/mid

View file

@ -0,0 +1,98 @@
#include "midiplayer.h"
/**
* @file
* @brief Jednoduchý přehrávač midi souborů.
*
* Kompletní midi obsahuje zvukové fonty, které jsou obrovské. Tohle je velice zjednodušené,
* takže docela dobře přehrává skladby typu ragtime, orchestrální midi jsou skoro nepoužitelné.
* Přesto se to pro jednoduché zvuky může hodit, protože je to poměrně nenáročné na systémové
* prostředky. Může to fungovat dokonce i na 8-bitovém uP.
* */
static constexpr unsigned AudioMidiDelay = 24;
static constexpr int INPUT_BIT_RANGE = 16;
static constexpr unsigned SIGMA_MASK = (1u << (INPUT_BIT_RANGE + 0)) - 1u;
static constexpr unsigned SIGNED_OFFEST = (1u << (INPUT_BIT_RANGE - 1));
// Předpokládá se na vstupu signed int o šířce INPUT_BIT_RANGE
// přičemž 0 na vstupu odpovídá MAXPWM / 2 na výstupu. Vypadá to divně, ale funguje.
static unsigned pwm_sd (const int input) {
static unsigned sigma = 0; // podstatné je, že proměnná je statická
const unsigned sample = (input + SIGNED_OFFEST) * MAXPWM;
sigma &= SIGMA_MASK; // v podstatě se odečte hodnota PWM
sigma += sample; // integrace prostým součtem
return sigma >> INPUT_BIT_RANGE;
}
/******************************************************************/
/// Konstruktor
MidiPlayer::MidiPlayer(GpioClass & io) noexcept : OneWay<uint16_t>(), led (io), passcnt (0u), scores(), melody() {
pause = 0;
scores = 0;
melody = * scores++;
running = false;
}
/// Počítá další vzorek
short MidiPlayer::nextSample (void) {
if (pause) pause -= 1; // Časování tónu
else ToneChange(); // Nový tón - MidiPlayer::ToneChange
return genSample ();
}
void MidiPlayer::pass() {
const bool b = passcnt & 0x100000;
led << b;
passcnt += 1u;
}
unsigned MidiPlayer::Send (uint16_t * const ptr, const unsigned len) {
const bool b = false; // but;
if (!b and !running) running = true;
if (!running) {
for (unsigned n=0; n<len; n++) ptr [n] = MAXPWM >> 1;
return len;
}
for (unsigned n=0; n<len; n++) {
const short s = nextSample();
ptr [n] = pwm_sd (s);
}
return len;
}
void MidiPlayer::stop (void) {
running = false; // na konci každé melodie stop
melody = * scores++;
if (!melody) {
scores = 0;
melody = * scores++;
running = false; // test bez tlačítka
}
}
void MidiPlayer::ToneChange (void) {
unsigned char midt;
for (;;) { // Pro všechny tóny před pauzou
unsigned char cmd = * melody++;
if (cmd & 0x80) { // event
const unsigned geno = cmd & 0x0F;
cmd >>= 4;
switch (cmd) {
case 0x8: // off
gens[geno].setMidiOff();
break;
case 0x9: // on
midt = * melody++;
gens[geno].setMidiOn (midt);
break;
default:
stop();
return; // melodie končí eventem 0xf0
}
} else { // pause
midt = * melody++;
// Když to trochu uteče, zase se z toho nestřílí, tak to nechme být.
pause = ((unsigned int) cmd << 8) + midt; // v ms
pause *= AudioMidiDelay; // ale máme vzorkování cca 24 kHz
return;
}
}
}

View file

@ -0,0 +1,45 @@
#ifndef DACPLAYER_H
#define DACPLAYER_H
#include "oneway.h"
#include "gpio.h"
#include "tone.h"
#include "audio.h"
#include "pwmclass.h"
#include "cache.h"
/// Třída, která hraje čistě na pozadí.
class MidiPlayer : public OneWay<uint16_t> {
// Veřejné metody
public:
/// Konstruktor
explicit MidiPlayer (GpioClass & io) noexcept;
unsigned Send (uint16_t * const ptr, const unsigned len) override;
void stop ();
void pass ();
protected:
// Chráněné metody
/// Obsluha tónu
void ToneChange (void);
/// Obsluha vzorku
short nextSample (void);
/// Generuj vzorek pro všechny tóny @return Vzorek
short genSample (void) {
int res = 0;
for (unsigned int i=0; i<maxGens; i++) res += gens[i].step();
// Pro jistotu omezíme - předejdeme chrastění
if (res > maxValue) res = maxValue;
if (res < minValue) res = minValue;
return (res);
}
private:
GpioClass & led;
Tone gens[maxGens]; /// Generátory tónů
unsigned passcnt;
volatile bool running;
Cache<uint32_t, 8u> scores;
Cache<uint8_t, 16u> melody;
volatile int pause;
};
#endif // DACPLAYER_H

View file

@ -0,0 +1,82 @@
//#include "string.h"
#include "norflash.h"
extern "C" {
extern void delay_init ();
extern void Delay_Ms (const unsigned dly);
};
union FlashCommandHeader {
struct {
FLASH_COMMANDS cmd : 8;
uint32_t adr : 24; // adresa je BIG ENDIEN - vyšší byte vystupuje po SPI dříve (MSB FIRST).
}__attribute__((packed));
uint8_t bytes [4];
}__attribute__((packed));
static inline uint32_t be_set_24 (const uint32_t p) {
return ((p & 0x000000ff) << 16)
| ((p & 0x0000ff00) << 0)
| ((p & 0x00ff0000) >> 16);
}
static constexpr unsigned FlashBlockLen = 0x1000u; // 4KiB = 4096 B
NorFlash::NorFlash() noexcept : spi() {
delay_init();
}
unsigned int NorFlash::ReadBlock(const unsigned int addr, uint8_t * data, const unsigned int len) {
FlashCommandHeader header;
header.cmd = FLASH_4READ;
header.adr = be_set_24(addr);
spi.ChipSelect(true);
for (unsigned n=0u; n<sizeof(header); n++) spi.ReadWriteByte(header.bytes[n]);
for (unsigned n=0u; n<len; n++) data [n] = spi.ReadWriteByte(0xff);
spi.ChipSelect(false);
return len;
}
unsigned int NorFlash::WriteBlock(const unsigned int addr, const uint8_t * data, const unsigned int len) {
FlashCommandHeader header;
header.cmd = FLASH_4PP;
header.adr = be_set_24(addr);
SingleCommand(FLASH_WREN);
spi.ChipSelect(true);
for (unsigned n=0u; n<sizeof(header); n++) spi.ReadWriteByte(header.bytes[n]);
for (unsigned n=0u; n<len; n++) spi.ReadWriteByte(data [n]);
spi.ChipSelect(false);
if (!WaitForReady()) return 0;
return len;
}
bool NorFlash::EraseSector(const unsigned int addr) {
FlashCommandHeader header;
header.cmd = FLASH_4SE;
header.adr = be_set_24(addr);
SingleCommand(FLASH_WREN);
spi.ChipSelect(true);
for (unsigned n=0u; n<sizeof(header); n++) spi.ReadWriteByte(header.bytes[n]);
spi.ChipSelect(false);
if (!WaitForReady(400u)) return false;
return true;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
inline bool NorFlash::SingleCommand(const FLASH_COMMANDS cmd) const {
uint8_t tmp = cmd;
spi.ChipSelect(true);
spi.ReadWriteByte (tmp);
spi.ChipSelect(false);
return true;
}
inline uint8_t NorFlash::ReadStatus(const FLASH_COMMANDS cmd) const {
uint8_t buf [2] = {cmd, 0};
spi.ChipSelect(true);
for (unsigned n=0u; n<2u; n++) buf[n] = spi.ReadWriteByte(buf[n]);
spi.ChipSelect(false);
return buf[1];
}
bool NorFlash::WaitForReady(const unsigned int ms) const {
uint8_t sr;
for (unsigned n=0; n<ms; n++) {
Delay_Ms (1u);
sr = ReadStatus(FLASH_RDSR1);
if ((sr & 1u) == 0u) return true;
}
return false;
}

40
V203F6P6/midi/norflash.h Normal file
View file

@ -0,0 +1,40 @@
#ifndef NORFLASH_H
#define NORFLASH_H
#include <stdint.h>
#include "spiblocked.h"
/// Enumerace povelů pro SPI FLASH.
enum FLASH_COMMANDS : uint8_t {
/* single byte commands */
FLASH_WRDI = 0x04, // nepoužito
FLASH_WREN = 0x06,
FLASH_RDID = 0x9F,
FLASHRSTEN = 0x66, // nepoužito
FLASH__RST = 0x99, // nepoužito
FLASH__RES = 0xAB, // release from power down, nepoužito
FLASH__DPD = 0xB9, // power down, nepoužito
// multi - byte
FLASH_RDSR1 = 0x05,
// dále se mohou povely lišit
FLASH_RDSR2 = 0x35, // nepoužito
FLASH_4READ = 0x03,
FLASH_4PP = 0x02,
FLASH_4SE = 0x20,
};
class NorFlash {
SpiBlocked spi;
public:
explicit NorFlash () noexcept;
unsigned ReadBlock (const unsigned addr, uint8_t * data, const unsigned len);
unsigned WriteBlock (const unsigned addr, const uint8_t * data, const unsigned len);
bool EraseSector (const unsigned addr);
protected:
/// Jednoduchý příkaz
bool SingleCommand (const FLASH_COMMANDS cmd) const;
/// Načtení registru STATUS1 nebo STATUS2 pomocí FLASH_RDSRx
uint8_t ReadStatus (const FLASH_COMMANDS cmd) const;
/// Čekání na dokončení operace zápisu / mazání
bool WaitForReady (const unsigned ms = 5u) const;
};
#endif // NORFLASH_H

110
V203F6P6/midi/pwmclass.cpp Normal file
View file

@ -0,0 +1,110 @@
#include "pwmclass.h"
#include "gpio.h"
typedef __SIZE_TYPE__ size_t;
extern "C" {
[[gnu::interrupt]] extern void DMA1_Channel2_IRQHandler( void );
};
static PwmClass * pPwmInstance = nullptr;
void DMA1_Channel2_IRQHandler( void ) {
DMA1_Type::INTFR_DEF state (DMA1.INTFR);
if (state.B.GIF2 != RESET) {
DMA1.INTFCR.B.CGIF2 = SET;
} else return;
if (state.B.HTIF2 != RESET) {
DMA1.INTFCR.B.CHTIF2 = SET;
if (pPwmInstance) pPwmInstance->send(false);
}
if (state.B.TCIF2 != RESET) {
DMA1.INTFCR.B.CTCIF2 = SET;
if (pPwmInstance) pPwmInstance->send(true);
}
}
/*
* initialize TIM2 for PWM
*/
inline void PwmClass::TimInit() noexcept {
// Enable GPIOA and TIM1
RCC.APB2PCENR.modify([] (RCC_Type::APB2PCENR_DEF & r) -> auto {
r.B.IOPAEN = SET;
r.B.IOPBEN = SET;
//r.B.AFIOEN = SET;
return r.R;
});
RCC.APB1PCENR.B.TIM2EN = SET;
// PA2 is TIM2_CH3, 10MHz Output alt func, push-pull
GPIOA.CFGLR.modify([](GPIOA_Type::CFGLR_DEF & r) -> auto {
r.B.CNF2 = 2u;
r.B.MODE2 = 1u;
return r.R;
});
// PB1 is DEN, active H Output 10 MHz, push-pull
GPIOB.CFGLR.modify([](GPIOA_Type::CFGLR_DEF & r) -> auto {
r.B.CNF1 = 0u;
r.B.MODE1 = 1u;
return r.R;
});
GPIOB.BSHR.B.BS1 = SET; // set to H
// Reset TIM2 to init all regs
RCC.APB1PRSTR.B.TIM2RST = SET;
RCC.APB1PRSTR.B.TIM2RST = RESET;
// CTLR1: default is up, events generated, edge align
// SMCFGR: default clk input is CK_INT
// Prescaler
TIM2.PSC.R = 0u; // 144 MHz
// Auto Reload - sets period
TIM2.ATRLR.R = MAXPWM - 1; // 24 kHz
// CH3 Mode is output, PWM1 (CC3S = 00, OC3M = 110)
TIM2.CHCTLR2_Output.modify([](TIM2_Type::CHCTLR2_Output_DEF & r) -> auto {
r.B.OC3M = 0x6u;
return r.R;
});
// Enable TIM1 outputs
TIM2.CCER.modify([](TIM2_Type::CCER_DEF & r) -> auto {
// Enable CH3, CH3 output, positive pol
r.B.CC3E = SET;
//r.B.CC3P = SET; // negative
return r.R;
});
// Reload immediately + Trigger DMA
TIM2.SWEVGR.B.UG = SET;
TIM2.DMAINTENR.B.UDE = SET;
}
inline void PwmClass::DmaInit() noexcept {
// Enable DMA
RCC.AHBPCENR.modify([](RCC_Type::AHBPCENR_DEF & r) -> auto {
r.B.SRAMEN = SET;
r.B.DMA1EN = SET;
return r.R;
});
// DMA can be configured to attach to T2UP
// The system can only DMA out at ~2.2MSPS. 2MHz is stable.
DMA1.CNTR2 .R = FULL_LEN;
DMA1.MADDR2.R = reinterpret_cast<size_t>(buffer);
DMA1.PADDR2.R = reinterpret_cast<size_t>(& TIM2.CH3CVR);
DMA1.CFGR2.modify([](DMA1_Type::CFGR2_DEF & r) -> auto {
r.B.DIR = SET; // MEM2PERIPHERAL
r.B.PL = 2u; // High priority.
r.B.PSIZE = 1u; // 16-bit peripheral
r.B.MSIZE = 1u; // 16-bit memory
r.B.MINC = SET; // Increase memory.
r.B.CIRC = SET; // Circular mode.
r.B.HTIE = SET; // Half-trigger
r.B.TCIE = SET; // Whole-trigger
// Enable DMA1 CH2
r.B.EN = SET;
return r.R;
});
}
PwmClass::PwmClass() noexcept : count(0u), pL(buffer), pH(buffer + HALF_LEN), src(nullptr) {
pPwmInstance = this;
TimInit ();
DmaInit ();
// Enable TIM2
}
void PwmClass::attach(OneWay<uint16_t>& s) {
NVIC.EnableIRQ (DMA1_Channel2_IRQn);
TIM2.CTLR1.B.CEN = SET;
src = & s;
}

35
V203F6P6/midi/pwmclass.h Normal file
View file

@ -0,0 +1,35 @@
#ifndef PWMCLASS_H
#define PWMCLASS_H
#include "system.h"
#include "oneway.h"
static constexpr unsigned MAXPWM = 6000u;
/* Používá TIM2, PWM kanál 3, DMA1 kanál 2, přerušení DMA1_Channel2_IRQHandler */
class PwmClass {
static constexpr unsigned HALF_LEN = 0x80u;
static constexpr unsigned FULL_LEN = 2u * HALF_LEN;
volatile unsigned count;
uint16_t * const pL;
uint16_t * const pH;
uint16_t buffer [FULL_LEN];
OneWay<uint16_t> * src;
public:
explicit PwmClass () noexcept;
void attach (OneWay<uint16_t> & s);
void send (const bool b) {
if (!src) return;
if (b) src->Send (pH, HALF_LEN);
else src->Send (pL, HALF_LEN);
if (count) count -= 1u;
}
void delay (const unsigned frames = 50u) {
count = frames;
while (count) {
asm volatile ("nop");
}
}
protected:
void DmaInit () noexcept;
void TimInit () noexcept;
};
#endif // PWMCLASS_H

View file

@ -0,0 +1,66 @@
#include "system.h"
#include "spiblocked.h"
enum SPICLK : uint32_t {
FPCLK_2 = 0u, // 72 MHz
FPCLK_4, // 36 MHz
FPCLK_8, // 18 MHz
FPCLK_16, // 9 MHz
FPCLK_32, // 4.5 MHz
FPCLK_64, // 2.25 MHz
FPCLK_128, // 1.125 MHz
FPCLK_256, // 0.5625 MHz
};
static constexpr unsigned FM = 3u; // 50 MHz
static void InitPins () noexcept {
// PA4 - NSS, PA5 - SCK, PA6 - MISO, PA7 - MOSI
GPIOA.CFGLR.modify([](GPIOA_Type::CFGLR_DEF & r) -> uint32_t {
r.B.MODE4 = FM;
r.B.CNF4 = 0u; // gen push - pull
r.B.MODE5 = FM;
r.B.CNF5 = 2u; // alt push - pull
r.B.MODE6 = 0u; // input mode
r.B.CNF6 = 1u; // floating
r.B.MODE7 = FM;
r.B.CNF7 = 2u; // alt push - pull
return r.R;
});
// AFIO - default
GPIOA.BSHR.B.BS4 = SET;
}
SpiBlocked::SpiBlocked() noexcept {
RCC.APB2PCENR.modify([](RCC_Type::APB2PCENR_DEF & r) -> uint32_t {
r.B.SPI1EN = SET;
r.B.IOPAEN = SET;
//r.B.AFIOEN = SET;
return r.R;
});
InitPins();
RCC.APB2PRSTR.B.SPI1RST = SET;
RCC.APB2PRSTR.B.SPI1RST = RESET;
SPI1.CTLR1.modify([](SPI1_Type::CTLR1_DEF & r) -> uint32_t {
r.B.CPHA = RESET;
r.B.CPOL = RESET;
r.B.MSTR = SET; // master
r.B.DFF = RESET; // 8 bit
r.B.SSM = SET; // software
r.B.SSI = SET; // !!! netuším proč, ale jinak se nenastaví MSTR a SPE
r.B.LSBFIRST = RESET;
r.B.BR = FPCLK_64;
return r.R;
});
SPI1.CRCR.R = 7u;
SPI1.CTLR1.B.SPE = SET;
}
uint8_t SpiBlocked::ReadWriteByte(const uint8_t data) const {
while (SPI1.STATR.B.TXE == RESET);
SPI1.DATAR.B.DATAR = data;
while (SPI1.STATR.B.RXNE == RESET);
return SPI1.DATAR.B.DATAR;
}
void SpiBlocked::ChipSelect(const bool on) const {
if (on) GPIOA.BSHR.B.BR4 = SET;
else GPIOA.BSHR.B.BS4 = SET;
}

View file

@ -0,0 +1,12 @@
#ifndef SPIBLOCKED_H
#define SPIBLOCKED_H
#include <stdint.h>
class SpiBlocked {
public:
explicit SpiBlocked () noexcept;
uint8_t ReadWriteByte (const uint8_t data) const;
void ChipSelect (const bool on) const;
};
#endif // SPIBLOCKED_H

76
V203F6P6/midi/ton/gen.cpp Normal file
View file

@ -0,0 +1,76 @@
#include <stdio.h>
#include <math.h>
#include "../audio.h"
static constexpr int maxTone = (1L<<23) - 1;
int limit (double tone) {
int k = (int) round (tone);
if (k > maxTone) k = 0;
return k;
}
int normalize (double val, double scale) {
return (int) round (val * scale);
}
int main (void) {
double base, dint;
int i,j;
base = 8.1757989156; // C5 v Hz (http://www.tonalsoft.com/pub/news/pitch-bend.aspx)
base *= (double)(1UL << 24) / double (AudioSampleRate);
dint = pow(2.0, 1.0 / 12.0);
FILE* out = fopen ("miditone.c","w");
// Tabulka inkrementů pro midi tóny
fprintf (out, "const unsigned int midiTones[] = {\n");
for (i=0,j=0; i<127; i++) {
fprintf (out, "%8d, ", limit (base));
if (++j >= 12) {
j = 0;
fprintf (out, "\n");
}
base *= dint;
}
fprintf (out, "%8d };\n\n", limit (base));
// Vzorky pro jednu periodu tónu včetně barvy
double samples [256], max = 0.0, val;
base = M_PI / 128.0;
for (i=0; i<256; i++) {
val = 0.0;
val += 1.0 * sin (1.0 * base * (double) i);
// Je dobré přidat nějaké harmonické, jinak je tón chudý
val += 0.3 * sin (2.0 * base * (double) i);
// 7. harmonická je nepříjemná, zkuste si to.
// val += 0.5 * sin (7.0 * base * (double) i);
if (val > +max) max = +val;
if (val < -max) max = -val;
samples [i] = val;
}
max = (double)(0x1FFF) / max; // normála do 14. bitů
// mormalizace a výpis
fprintf (out, "const short onePeriod[] = {\n");
for (i=0,j=0; i<255; i++) {
fprintf (out, "%6d, ", normalize (samples[i], max));
if (++j >= 8) {
j = 0;
fprintf (out, "\n");
}
base *= dint;
}
fprintf (out, "%6d };\n\n", normalize (samples[i], max));
unsigned Attack = maxAmplt;
fprintf (out, "const unsigned attackTable[] = {\n");
for (i=0,j=0; i<127; i++) {
fprintf (out, "0x%08X, ", Attack);
if (++j >= 8) {
j = 0;
fprintf (out, "\n");
}
Attack -= Attack / 20;
}
fprintf (out, "0x%08X };\n\n", Attack);
fclose (out);
}

69
V203F6P6/midi/tone.cpp Normal file
View file

@ -0,0 +1,69 @@
#include "tone.h"
/**
* Přidán attack - zmizí rušivé lupání, prodlouží se obsluha tónu.
* */
extern "C" const short onePeriod[];
extern "C" const unsigned int midiTones[];
extern "C" const unsigned int attackTable[];
static constexpr unsigned defFall = 16u;
static constexpr unsigned maxAttack = 127u;
Tone::Tone() noexcept {
ampl = 0; freq = 0; base = 0; atck = 0;
fall = defFall;
}
void Tone::setAmpl (unsigned int a) {
ampl = a;
}
void Tone::setFreq (unsigned int f) {
freq = f;
}
void Tone::setMidiOn (unsigned int m) {
freq = midiTones [m & 0x7F];
if (freq) atck = maxAttack;
fall = 1;
}
void Tone::setMidiOff (void) {
fall = defFall;
/*
base = 0;
freq = 0;
*/
}
void Tone::setFall (unsigned int f) {
fall = f;
}
int Tone::step (void) {
unsigned int k,t;
int y;
// Spočteme index x pro přístup do tabulky
const unsigned x = (base >> 16) & 0xFF;
y = onePeriod [x]; // vzorek vezmeme z tabulky
// k je horní půlka amplitudy
k = ampl >> 16;
y *= k; // vzorek násobíme amplitudou (tedy tím vrškem)
y >>= 12; // a vezmeme jen to, co potřebuje DAC
k *= fall; // Konstanta fall určuje rychlost poklesu amplitudy,
// čím více, tím je rychlejší. Pokud by bylo 1, pokles je 2^16 vzorků, což už je pomalé.
base += freq; // časová základna pro další vzorek
if (atck) { // přidán attack = náběh amplitudy
t = attackTable [atck]; // z tabulky
if (t > ampl) ampl = t; // prevence lupání - nemí být skok amplitudy
atck -= 1; // dočasovat k nule
} else
ampl -= k; // exponenciální pokles amplitudy
// a je to
return y;
}

26
V203F6P6/midi/tone.h Normal file
View file

@ -0,0 +1,26 @@
#ifndef TONE_H
#define TONE_H
class Tone {
public:
explicit Tone () noexcept;
void setMidiOn (unsigned int m);
void setMidiOff (void);
void setFreq (unsigned int f);
void setAmpl (unsigned int a);
void setFall (unsigned int f);
int step (void);
private:
/// Amplituda tónu, interní proměnná
unsigned int ampl;
/// Exponenciální doběh - čím víc, tím rychlejší (0 = stálý)
unsigned int fall;
/// Frekvence (normalizovaná)
unsigned int freq;
/// Přetékající index do tabulky vzorků
unsigned int base;
/// Attack = index do tabulky attackTable
unsigned int atck;
};
#endif // TONE_H

View file

@ -0,0 +1,80 @@
/********************************** (C) COPYRIGHT *******************************
* File Name : usb_desc.c
* Author : WCH
* Version : V1.0.0
* Date : 2022/08/20
* Description : usb device descriptor,configuration descriptor,
* string descriptors and other descriptors.
*********************************************************************************
* Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
* Attention: This software (modified or not) and binary are used for
* microcontroller manufactured by Nanjing Qinheng Microelectronics.
*******************************************************************************/
#include "usb_desc.h"
/* Device Descriptor */
const uint8_t USBD_DeviceDescriptor[] =
{
0x12, // bLength
0x01, // bDescriptorType (Device)
0x10, 0x01, // bcdUSB 1.10
0x02, // bDeviceClass
0x00, // bDeviceSubClass
0x00, // bDeviceProtocol
DEF_USBD_UEP0_SIZE, // bMaxPacketSize0 64
(uint8_t)DEF_USB_VID, (uint8_t)(DEF_USB_VID >> 8), // idVendor 0x1A86
(uint8_t)DEF_USB_PID, (uint8_t)(DEF_USB_PID >> 8), // idProduct 0x5537
DEF_IC_PRG_VER, 0x00, // bcdDevice 0.01
0x01, // iManufacturer (String Index)
0x02, // iProduct (String Index)
0x03, // iSerialNumber (String Index)
0x01, // bNumConfigurations 1
};
/* Configuration Descriptor */
const uint8_t USBD_ConfigDescriptor[] =
{
/* Configure descriptor */
0x09, 0x02, 0x43, 0x00, 0x02, 0x01, 0x00, 0x80, 0x32,
/* Interface 0 (CDC) descriptor */
0x09, 0x04, 0x00, 0x00, 0x01, 0x02, 0x02, 0x01, 0x00,
/* Functional Descriptors */
0x05, 0x24, 0x00, 0x10, 0x01,
/* Length/management descriptor (data class interface 1) */
0x05, 0x24, 0x01, 0x00, 0x01,
0x04, 0x24, 0x02, 0x02,
0x05, 0x24, 0x06, 0x00, 0x01,
/* Interrupt upload endpoint descriptor */
0x07, 0x05, 0x81, 0x03, (uint8_t)DEF_USBD_ENDP1_SIZE, (uint8_t)( DEF_USBD_ENDP1_SIZE >> 8 ), 0x01,
/* Interface 1 (data interface) descriptor */
0x09, 0x04, 0x01, 0x00, 0x02, 0x0A, 0x00, 0x00, 0x00,
/* Endpoint descriptor */
0x07, 0x05, 0x02, 0x02, (uint8_t)DEF_USBD_ENDP2_SIZE, (uint8_t)( DEF_USBD_ENDP2_SIZE >> 8 ), 0x00,
/* Endpoint descriptor */
0x07, 0x05, 0x83, 0x02, (uint8_t)DEF_USBD_ENDP3_SIZE, (uint8_t)( DEF_USBD_ENDP3_SIZE >> 8 ), 0x00,
};
#define DEF_STRDESC(p,n) w_text<(sizeof(p)>>1)>n={sizeof(n)-2u,3u,{p}}
template<const unsigned N> struct w_text {
uint8_t len, typ;
const char16_t str [N];
};
static const DEF_STRDESC((u"Kizarm Labs."), str_1);
static const DEF_STRDESC((u"Flash Programmer"),str_2);
static const DEF_STRDESC((u"0001"), str_3);
/* Language Descriptor */
static const uint8_t LangDescr[] = {
0x04, 0x03, 0x09, 0x04
};
const uint8_t * USBD_StringLangID = reinterpret_cast<const uint8_t *>(LangDescr);
const uint8_t * USBD_StringVendor = reinterpret_cast<const uint8_t *>(&str_1);
const uint8_t * USBD_StringProduct = reinterpret_cast<const uint8_t *>(&str_2);
const uint8_t * USBD_StringSerial = reinterpret_cast<const uint8_t *>(&str_3);