Написал недавно программный USB-HOST на esp32 для работы с клавиатурой/мышкой/джойстиком. Процессор быстрый, но нежный, 5 вольт на ножках не выдерживает. Поэтому решил переписать на stm32f103c8t6, широко известную в варианте отладочной платы "Blue Pill".
К сожалению , это весьма неторопливый по сегодняшним меркам процессор(72 MHz vs 240 у esp32 ), поэтому были сомнения , смогу ли я обеспечить необходимую точность временного интервала между битами при передаче (1.5 Mbps +/- 1.5%),что соответствует +/- 0.01uS то есть примерно один такт работы процессора. То есть процедура задежки типа :
static inline void cpuDelay(uint32_t ticks)
{
uint32_t stop =_getCycleCount32() + ticks;
while((_getCycleCount32() - stop)&0x80000000u); // соntinue while overflow
}
необходимую точность обеспечивать перестала, поэтому решил перейти к процедуре вида:
void cpuDelay()
{
__asm__ __volatile__("nop");
__asm__ __volatile__("nop");
__asm__ __volatile__("nop");
__asm__ __volatile__("nop");
}
с необходимым числом "nop" - ов. Когда надоело настраивать задержку вручную и перекомпилировать код, решил написать процедуру подбирающую необходимую задержку, работающую в широком интервале частот процессора. Идея в следующем: вместо входа в процедуру с адресом cpuDelay войти в процедуру в середине, по указателю:
#define TNOP1 { __asm__ __volatile__(" nop"); }
#define TNOP2 {TNOP1 TNOP1}
#define TNOP4 {TNOP2 TNOP2}
#define TNOP8 {TNOP4 TNOP4}
#define TNOP16 {TNOP8 TNOP8}
#define TNOP32 {TNOP16 TNOP16}
#define TNOP64 {TNOP32 TNOP32}
__volatile__ void cpuDelayBase()
{
TNOP64;
TNOP64;
TNOP64;
TNOP64;
END_BASE:;
}
void (*delay_pntA)() = &cpuDelayBase;
#define cpuDelay(x) {(*delay_pntA)();}
#define SIZEOF_NOP 2
void setDelay(uint8_t ticks)
{
delay_pntA = (&cpuDelayBase)+((256-ticks)*SIZEOF_NOP);
}
Ну а теперь каждом запуске программы генерируется тестовая последовательность бит на 200,посылается, измеряется затраченное время и методом деления отрезка пополам процедура задержки приводится к требуемой и сохраняется в указателе delay_pntA.
Время передачи 6 бит должно составлять точно 4.0uS. 6 бит было удобно замерять на временной диаграмме передачи. В стартовой последовательности присутствует комбинация 6 фронтов , которые хорошо видно на диаграмме:
Диаграмма
![](https://habrastorage.org/getpro/habr/upload_files/312/d2f/b0e/312d2fb0ec78c7121d5b1a44f0c6ab09.jpg)
При калибровке выяснилось, что есть небольшой оверхед на измерениях, и его нормированная величина 0.12uS отсюда целевое время 4.12 uS.
А причем здесь ВОДКА!!!???
Дело в том , что давным давно, в CCCP водка была универсальной валютой, ею расплачивались за все, включая то , что нельзя было купить за деньги. Например, наш мастер участка по ремонту электроники не мог купить на заводе нужные нам детали официально, однако легко мог поменять ящик водки на ящик нужных нам микросхем , на том же заводе, подойдя к правильному человеку.
Цену на водку люди знали лучше числа Пи. В частности, на пивзаводе где я подрабатывал электриком во время учебы, телефон фельдшера был 2-87, а электриков-механиков 3-62, а дирекции 4-12. И никто не забывал эти номера телефонов. Цену в 2.87 я не застал по причине слишком молодого возраста а 3.62 (Русская ) и 4.12 (Столичная) - уже вполне. Цена складывалась из цены собственно водки и залоговой цены бутылки в 12 копеек, поэтому такая и не круглая.
Водка русская ,этикетка:
![](https://habrastorage.org/getpro/habr/upload_files/898/e48/583/898e485831b872de512e6ccd5a861404.jpg)
Водка столичная , этикетка:
![](https://habrastorage.org/getpro/habr/upload_files/13f/ad7/bdf/13fad7bdfb5f96189bfaea94feb61917.jpg)
Итак 4.0 - содержание и 0.12-емкость итого 4.12 ---- Это цена водки Столичная в 1981 году.
результат прогона:
pins 8 9 1 1 is OK!
cpu freq = 72.000000 MHz
TIME_MULT = 43
120 bits in 57.333332 uSec 2.093023 MHz 6 ticks in 2.866667 uS
120 bits in 269.000000 uSec 0.446097 MHz 6 ticks in 13.450000 uS
120 bits in 162.333328 uSec 0.739220 MHz 6 ticks in 8.116667 uS
120 bits in 109.000000 uSec 1.100917 MHz 6 ticks in 5.450000 uS
120 bits in 82.916664 uSec 1.447236 MHz 6 ticks in 4.145833 uS
120 bits in 69.000000 uSec 1.739130 MHz 6 ticks in 3.450000 uS
120 bits in 75.666664 uSec 1.585903 MHz 6 ticks in 3.783333 uS
120 bits in 75.666664 uSec 1.585903 MHz 6 ticks in 3.783333 uS
120 bits in 77.333336 uSec 1.551724 MHz 6 ticks in 3.866667 uS
120 bits in 77.333336 uSec 1.551724 MHz 6 ticks in 3.866667 uS
TRANSMIT_TIME_DELAY = 15 time = 4.145833 error = 0.627029%
USB1: Ack = 0 Nack = 0 20 pcurrent->cb_Cmd = 2 state = 2 epCount = 0
USB2: Ack = 0 Nack = 0 00 pcurrent->cb_Cmd = 0 state = 0 epCount = 0
desc.bDeviceClass = 00
desc.bNumConfigurations = 01
cfg.bLength = 09
cfg.wLength = 59
cfg.bNumIntf = 02
cfg.bCV = 01
cfg.bIndex = 00
cfg.bAttr = a0
cfg.bMaxPower = 50
pcurrent->epCount = 1
pcurrent->epCount = 2
desc.bDeviceClass = 00
desc.bNumConfigurations = 01
cfg.bLength = 09
cfg.wLength = 34
cfg.bNumIntf = 01
cfg.bCV = 01
cfg.bIndex = 00
cfg.bAttr = a0
cfg.bMaxPower = 50
pcurrent->epCount = 1
USB0: Ack = 6 Nack = 0 80 pcurrent->cb_Cmd = 14 state = 100 epCount = 2
USB1: Ack = 6 Nack = 0 20 pcurrent->cb_Cmd = 14 state = 104 epCount = 1
P.S. Водка водкой, у нас в Израиле разрешили коноплю, забыл выложить исходники, вот.
P.P.S. не придирайтесь к коду. Это только proof of concept. Будет чиститься.