Зачастую если в устройстве есть программируемая логика, присутствует и процессор/микроконтроллер.
В какой-то момент мне надоело разводить на платах разъем JTAG, он занимает много места на плате и по сути нужен только для разработки. В конечном устройстве он вообще без надобности.
Очень часто для проверки правильности реализации Verilog кода или вообще "посмотреть как сигнальчики бегают" я использую SignalTap II Logic Analyzer, штука удобная и наглядная, я думаю многие сразу узнают по изображению:
Но как же отлаживать само устройство и в частности программируемую логику без JTAG?
Я уж молчу о записи прошивок в CPLD на этапе производства.
А у нас есть микроконтроллер.
Возьмем к примеру микроконтроллер STM32F103RCT6 и реализуем в нем USB Byte Blaster. И возьмем CPLD EPM3064.
Разумеется я имею ввиду что микроконтроллер уже подключен к соответствующим выводам программируемой логики, например вот так:
PC12->TDI
PC13->TMS
PC14->TCK
PC15->TDO
USB Устройство начинается с дескриптора, опишем его:
USB Descriptor
/* USB Standard Device Descriptor */
const BYTE USB_DeviceDescriptor[] = {
USB_DEVICE_DESC_SIZE, /* bLength */
USB_DEVICE_DESCRIPTOR_TYPE, /* bDescriptorType */
WBVAL(0x0110), /* 1.10 */ /* bcdUSB */
0x00, // Class Code
0x00, // Subclass code
0x00, // Protocol code
USB_MAX_PACKET0, // Max packet size for EP0, see usbcfg.h
WBVAL(0x09FB), /* idVendor */
WBVAL(0x6001), /* idProduct */
WBVAL(0x0400), /* 1.00 */ /* bcdDevice */
0x01, // Manufacturer string index
0x02, // Product string index
0x03, // Device serial number string index
0x01 // Number of possible configurations
};
/* USB Configuration Descriptor */
/* All Descriptors (Configuration, Interface, Endpoint, Class, Vendor */
const BYTE USB_ConfigDescriptor[] = {
/* Configuration Descriptor */
USB_CONFIGUARTION_DESC_SIZE, /* bLength */
USB_CONFIGURATION_DESCRIPTOR_TYPE, /* bDescriptorType */
/* Configuration 1 */
WBVAL( /* wTotalLength */
(1*USB_CONFIGUARTION_DESC_SIZE) +
(1*USB_INTERFACE_DESC_SIZE) +
(2*USB_ENDPOINT_DESC_SIZE)
),
1, // Number of interfaces in this cfg
1, // Index value of this configuration
0, // Configuration string index
USB_CONFIG_SELF_POWERED, /*|*/ /* bmAttributes */
USB_CONFIG_POWER_MA(80), /* bMaxPower */
/* Interface Descriptor */
USB_INTERFACE_DESC_SIZE, /* bLength */
USB_INTERFACE_DESCRIPTOR_TYPE, /* bDescriptorType */
/* Interface 0, Alternate Setting 0, MSC Class */
0x00, /* bInterfaceNumber */
0x00, /* bAlternateSetting */
0x02, /* bNumEndpoints */
0xFF, // Class code
0xFF, // Subclass code
0xFF, // Protocol code
0, // Interface string index
/* Endpoint Descriptor */
/* Bulk In Endpoint */
USB_ENDPOINT_DESC_SIZE, /* bLength */
USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType */
USB_ENDPOINT_IN(BLST_EP_IN & 0x0F), /* bEndpointAddress */
USB_ENDPOINT_TYPE_BULK, /* bmAttributes */
WBVAL(BLST_MAX_PACKET), /* wMaxPacketSize */
10, //Interval
USB_ENDPOINT_DESC_SIZE, /* bLength */
USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType */
USB_ENDPOINT_OUT(BLST_EP_OUT & 0x0F), /* bEndpointAddress */
USB_ENDPOINT_TYPE_BULK, /* bmAttributes */
WBVAL(BLST_MAX_PACKET), /* wMaxPacketSize */
10 //Interval
/* Terminator */
,0 /* bLength */
};
//Language code string descriptor
ROM struct{BYTE bLength;BYTE bDscType;WORD string[1];}sd000={
sizeof(sd000),USB_STRING_DESCRIPTOR_TYPE,{0x0409}};
//Manufacturer string descriptor
ROM struct{BYTE bLength;BYTE bDscType;WORD string[6];}sd001={
sizeof(sd001),USB_STRING_DESCRIPTOR_TYPE,
{'A','l','t','e','r','a'}};
//Product string descriptor
ROM struct{BYTE bLength;BYTE bDscType;WORD string[11];}sd002={
sizeof(sd002),USB_STRING_DESCRIPTOR_TYPE,
{'U','S','B','-','B','l','a','s','t','e','r'}};
//Serial string descriptor
ROM struct{BYTE bLength;BYTE bDscType;WORD string[8];}sd003={
sizeof(sd003),USB_STRING_DESCRIPTOR_TYPE,
{'0','0','0','0','0','0','0','0'}};
//Array of configuration descriptors
ROM BYTE *ROM USB_CD_Ptr[]=
{
(ROM BYTE *)&USB_ConfigDescriptor
};
//Array of string descriptors
ROM BYTE *ROM USB_SD_Ptr[]=
{
(ROM BYTE *)&sd000,
(ROM BYTE *)&sd001,
(ROM BYTE *)&sd002,
(ROM BYTE *)&sd003
};
/* USB String Descriptor (optional) */
const BYTE *USB_StringDescriptor = (const BYTE *)USB_SD_Ptr;
Далее нам нужно прописать саму "начинку" Byte Blaster'a:
blaster.c
BYTE fifo_wp,fifo_rp;
BYTE InFIFO[256];
BYTE nCS = 0;
DWORD PacketPos = 0;
BYTE InPacket[64];
BYTE OutPacket[128];
BYTE ep1_ready = 1;
void EP2CallBack(void)
{
ProcBlasterData();
}
void ProcBlasterData(void)
{
int bufptr = 0;
static BYTE jtag_byte = 0, read = 0, aser_byte = 0;
DWORD recv_byte;
BYTE acc0, acc1;
recv_byte = USB_ReadEP(BLST_EP_OUT, OutPacket);
bufptr = 0;
if(!recv_byte) return;
LED_RD_ON();
do
{
if (jtag_byte)
{
if (!read)
{
do
{
acc0 = OutPacket[bufptr++];
JTAG_Write(acc0);
jtag_byte--;
recv_byte--;
} while (jtag_byte && recv_byte);
}
else
{
do
{
acc0 = OutPacket[bufptr++];
acc1 = JTAG_RW(acc0);
enqueue(acc1);
jtag_byte--;
recv_byte--;
} while (jtag_byte&&recv_byte);
}
}
else if (aser_byte)
{
if (!read)
{
do
{
acc0 = OutPacket[bufptr++];
JTAG_Write(acc0);
aser_byte--;
recv_byte--;
} while (aser_byte&&recv_byte);
}
else
{
do {
acc0 = OutPacket[bufptr++];
acc1 = ASer_RW(acc0);
enqueue(acc1);
aser_byte--;
recv_byte--;
} while (aser_byte&&recv_byte);
}
}
else
{
do
{
acc0 = OutPacket[bufptr++];
_bitcopy(bitmask(acc0, 6), read);
if (bitmask(acc0, 7))
{ //EnterSerialMode
LTCK(0); //bug fix
if (nCS & 0x8)
{ //nCS=1:JTAG
jtag_byte = acc0 & 0x3F;
}
else
{ //nCS=0:ActiveSerial
aser_byte = acc0 & 0x3F;
}
/* Always JTAG Made */
recv_byte--;
break;
}
else
{ //BitBangMode
OUTP(acc0);
if (read) {
acc1 = 0;
if (PADO) acc1 |= 0x02;
if (PTDO) acc1 |= 0x01;
enqueue(acc1);
}
recv_byte--;
}
} while (recv_byte);
}
} while (recv_byte);
/* Disable RD LED */
LED_RD_OFF();
return;
}
void EP1CallBack(void)
{
ep1_ready = 1;
return;
}
void OUTP(BYTE b)
{
unsigned int uiPortState = GPIOC->ODR;
/* TCK - 0
TMS - 1
nCE - 2
nCS - 3
x - 5,6
TDI - 4,7
*/
if(b & (1 << 3)) /* nCS */
nCS = 0x08;
else
nCS = 0;
if(b & (1 << 0)) /* TCK */
uiPortState |= (1 << 14);
else
uiPortState &= ~(1 << 14);
if(b & (1 << 1)) /* TMS */
uiPortState |= (1 << 13);
else
uiPortState &= ~(1 << 13);
if(b & (1 << 4)) /* TDI */
uiPortState |= (1 << 12);
else
uiPortState &= ~(1 << 12);
GPIOC->ODR = uiPortState;
// Nop();
return;
}
void JTAG_Write(BYTE a)
{
int i;
for(i = 0;i < 8;i ++)
{
bitcopy(a & (0x01 << i),LTDI);
tck();
}
}
BYTE JTAG_RW(BYTE a)
{
BYTE bRet=0;
int i = 0;
for(i = 0;i < 8;i++)
{
bitcopy(a & (0x01 << i),LTDI);
if(PTDO) bRet |= (0x01 << i);
tck();
}
return bRet;
}
BYTE ASer_RW(BYTE a) {
BYTE bRet=0;
int i = 0;
for(i = 0;i < 8;i++)
{
bitcopy(a & (0x01 << 1),LTDI);
if(PADO) bRet|= (0x01 << 1);
tck();
}
return bRet;
}
extern USB_EP_DATA EP0Data;
BYTE bBuffer[10];
void USB_EP0BlasterReq(USB_SETUP_PACKET *SetupPacket)
{
BYTE bIndex;
if (SetupPacket->bmRequestType.BM.Dir == 0)
{ //0utput
//Responce by sending zero-length packet
//I don't know if this way is right, but working:)
USB_WriteEP(0x80, NULL, 0);
return;
}
if (SetupPacket->bRequest == 0x90)
{
bIndex = (SetupPacket->wIndex.WB.L << 1) & 0x7E;
bBuffer[0] = eeprom_read(bIndex);
bBuffer[1] = eeprom_read(bIndex + 1);
}
else
{
bBuffer[0] = 0x36;
bBuffer[1] = 0x83;
}
EP0Data.pData = bBuffer;
EP0Data.Count = 2;
// USB_WriteEP(0x80, bBuffer, 2);
return;
}
И в финале добавим обработчик в нашу программу. Можно это сделать конечно и в таймере, но для наглядности я добавил функцию обработки в main() в бесконечном цикле.
Смотрим код:
main.c
while (1)
{
if(USB_Configuration)
{
if(ep1_ready)
{
acc0 = fifo_used();
if (62 <= acc0)
{ //send full packet to host
LED_WR_ON();
ep1_ready = 0;
dequeue(&InPacket[2], 62);
USB_WriteEP(BLST_EP_IN, InPacket, 64);
ChargeTimer_ms(10);
}
else if(acc0 || IsTimerArrived())
{
if(acc0)
LED_WR_ON();
else
LED_WR_OFF();
ep1_ready = 0;
if(acc0)
dequeue(&InPacket[2], acc0);
USB_WriteEP(BLST_EP_IN, InPacket, acc0 + 2);
ChargeTimer_ms(10);
}
}
}
} // end while
Собираем всё вместе и компилируем и прошиваем, используя вот такой игровой набор:
И подключаем наше устройство к USB.
И это же конечно ничего не значит. Потому что установка происходит только на основании дескриптора устройства, теперь проверим действительно ли это у нас "настоящий" Byte Blaster.
Выбираем:
И запускаем JTAG Chain Debugger:
Работает!!! Можем писать CPLD прям на борту нашего устройства.
К слову сказать если подключить эту реализацию к FPGA, будет доступна запись в FPGA и SignalTap II. У меня всё.
Спасибо за внимание! Успехов в делах и отличного настроения!
DX168B
Я такой купил на всем известной торговой площадке Китая. У него была проблема. Штатный драйвер гнал Win10 в BSOD при попытке обращения к устройству. Я тогда порывшись на Github, нашел исходники бластера на STM32. Адаптировал код под железо этого адаптера и всё взлетело.
ramfactory Автор
Респект! Приятно услышать об удачной реализации.
Кстати, подобный проект есть у китайцев под чип PIC18F14K50.