Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add initial Serial Wire Debug (SWD) support #30

Merged
merged 1 commit into from
Jun 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 158 additions & 2 deletions JTAGulator.spin
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ CON
MENU_JTAG = 1 ' JTAG
MENU_UART = 2 ' UART
MENU_GPIO = 3 ' General Purpose I/O
MENU_SWD = 4 ' Serial Wire Debug (SWD)


VAR ' Globally accessible variables
Expand Down Expand Up @@ -91,7 +92,12 @@ VAR ' Globally accessible variables
long uStack[50] ' Stack space for passthrough cog

long gWriteValue ' Parameter for Write_IO_Pins


long swdClk ' SWD Pins (must stay in this order)
long swdIo
long swdPinsKnown ' Are above pins valid?
long swdFrequency

long chStart ' Channel range for the current scan (specified by the user)
long chEnd

Expand All @@ -107,13 +113,15 @@ OBJ
uart : "JDCogSerial" ' UART/Asynchronous Serial communication engine (Carl Jacobs, http://obex.parallax.com/object/298)
pt_in : "jm_rxserial" ' UART/Asynchronous Serial receive driver for passthrough (JonnyMac, https://forums.parallax.com/discussion/114492/prop-baudrates)
pt_out : "jm_txserial" ' UART/Asynchronous Serial transmit driver for passthrough (JonnyMac, https://forums.parallax.com/discussion/114492/prop-baudrates)
swd : "SWDHost" ' SWD Host module


PUB main | cmd
System_Init ' Initialize system/hardware
JTAG_Init ' Initialize JTAG-specific items
UART_Init ' Initialize UART-specific items
GPIO_Init ' Initialize GPIO-specific items
SWD_Init ' Initialize SWD-specific items

pst.CharIn ' Wait until the user presses a key before getting started
pst.Str(@InitHeader) ' Display header
Expand Down Expand Up @@ -142,6 +150,9 @@ PUB main | cmd

MENU_GPIO: ' General Purpose I/O
Do_GPIO_Menu(cmd)

MENU_SWD: ' Single Wire Debug
Do_SWD_Menu(cmd)

other:
idMenu := MENU_MAIN
Expand All @@ -164,6 +175,9 @@ PRI Do_Main_Menu(cmd)
"G", "g": ' Switch to GPIO submenu
idMenu := MENU_GPIO

"S", "s": ' Switch to SWD submenu
idMenu := MENU_SWD

"V", "v": ' Set target I/O voltage
Set_Target_IO_Voltage

Expand Down Expand Up @@ -270,6 +284,21 @@ PRI Do_GPIO_Menu(cmd)
Do_Shared_Menu(cmd)


PRI Do_SWD_Menu(cmd)
case cmd
"I", "i": ' Identify SWD pinout (IDCODE Scan)
if (vTargetIO == -1)
pst.Str(@ErrTargetIOVoltage)
else
SWD_IDCODE_Scan

"C", "c":
Set_SWD_Frequency

other:
Do_Shared_Menu(cmd)


PRI Do_Shared_Menu(cmd)
case cmd
"V", "v": ' Set target I/O voltage
Expand Down Expand Up @@ -298,6 +327,9 @@ PRI Display_Menu_Text

MENU_GPIO:
pst.Str(@MenuGPIO)

MENU_SWD:
pst.Str(@MenuSWD)

if (idMenu <> MENU_MAIN)
pst.Str(@MenuShared)
Expand All @@ -317,6 +349,9 @@ PRI Display_Command_Prompt

MENU_GPIO: ' General Purpose I/O
pst.Str(String("GPIO"))

MENU_SWD: ' Single Wire Debug
pst.Str(String("SWD"))

other:
idMenu := MENU_MAIN
Expand Down Expand Up @@ -1732,6 +1767,122 @@ PRI Display_IO_Pins(value) | count
pst.Str(String(" (0x"))
pst.Hex(value, g#MAX_CHAN >> 2)
pst.Str(String(")"))


CON {{ SWD METHODS }}

PRI SWD_Init
' Don't know any SWD pins yet.
swdPinsKnown := 0
swdClk := -1
swdIo := -1
swdFrequency := swd#SWD_FASTEST_CLOCK_RATE


PRI SWD_IDCODE_Scan | response, idcode, ctr, num, xclk, xio ' Identify SWD pinout (IDCODE Scan)
if (Get_Channels(2) == -1) ' Get the channel range to use
return
Display_Permutations((chEnd - chStart + 1), 2) ' SWCLK, SWDIO

if (Get_Settings == -1) ' Get configurable scan settings
return

pst.Str(@MsgPressSpacebarToBegin)
if (pst.CharIn <> " ")
pst.Str(@ErrIDCODEAborted)
return

pst.Str(@MsgJTAGulating)
u.TXSEnable ' Enable level shifter outputs
if (jPinsLow == 1)
u.Set_Pins_Low(chStart, chEnd) ' Set current channel range to output LOW
u.Pause(jPinsLowDelay) ' Delay to stay asserted

swd.init
num := 0 ' Counter of possibly good pinouts
ctr := 0 ' Counter of total loop iterataions.
repeat swdClk from chStart to chEnd ' For every possible pin permutation
repeat swdIo from chStart to chEnd
if (swdIo == swdClk)
next

if (pst.RxEmpty == 0) ' Abort scan if any key is pressed
SWD_Scan_Cleanup(num, xclk, xio)
pst.RxFlush
pst.Str(@ErrIDCODEAborted)
return

u.Set_Pins_High(chStart, chEnd) ' Set current channel range to output HIGH (in case there are active low signals that may affect operation, like TRST# or SRST#)
if (jPinsLow == 1)
u.Pause(jPinsHighDelay) ' Delay after deassertion before proceeding

' Use this pin mapping with the SWD module to attempt line resetting the device
' and reading out the IDCODE register.
swd.config(swdClk, swdIo, swdFrequency)
response := swd.resetSwJtagAndReadIdCode(@idcode)

' The IDCODE was most likely read out successfully with this pin mapping if
' the response code is OK (%001) and the least significant bit of the returned
' IDCODE is 1 (unless all bits of IDCODE are 1 which isn't valid).
if (response == swd#RESP_OK) and (idcode <> -1) and (idcode & 1)
Display_SWD_Pins
' Track this most recent detection results.
num++
xclk := swdClk
xio := swdIo
Display_Device_ID(idcode, 0, 0)
pst.Str(String(CR, LF))

' Progress indicator
++ctr
if (jPinsLow == 0)
Display_Progress(ctr, 100)
else
Display_Progress(ctr, 1)
u.Set_Pins_Low(chStart, chEnd) ' Set current channel range to output LOW
u.Pause(jPinsLowDelay) ' Delay to stay asserted

if (num == 0)
pst.Str(@ErrNoDeviceFound)
SWD_Scan_Cleanup(num, xclk, xio)

pst.Str(String(CR, LF, "IDCODE scan complete."))


PRI SWD_Scan_Cleanup(num, clk, io)
swd.uninit
if (num == 0) ' If no device(s) were found during the search
longfill(@swdClk, -1, 2) ' Clear SWD pinout
swdPinsKnown := 0
else ' Update globals with the most recent detection results
swdClk := clk
swdIo := io
swdPinsKnown := 1


PRI Display_SWD_Pins
pst.Str(String(CR, LF, "SWCLK: "))
pst.Dec(swdClk)
pst.Str(String(CR, LF, "SWDIO: "))
pst.Dec(swdIO)
pst.Str(String(CR, LF))


PRI Set_SWD_Frequency | value
pst.Str(String(CR, LF, "Current SWD clock frequency (Hz): "))
pst.Dec(swdFrequency)

pst.Str(String(CR, LF, "Enter new SWD clock frequency: "))
value := Get_Decimal_Pin

if (value < 1 OR value > swd#SWD_FASTEST_CLOCK_RATE)
pst.Str(@ErrOutOfRange)
else
swdFrequency := value
pst.Str(String(CR, LF, "New SWD clock frequency set: "))
pst.Dec(swdFrequency)




CON {{ OTHER METHODS }}
Expand Down Expand Up @@ -2006,7 +2157,8 @@ VersionInfo byte CR, LF, "JTAGulator FW 1.6", CR, LF
MenuMain byte CR, LF, "Target Interfaces:", CR, LF
byte "J JTAG/IEEE 1149.1", CR, LF
byte "U UART/Asynchronous Serial", CR, LF
byte "G GPIO", CR, LF, LF
byte "G GPIO", CR, LF
byte "S SWD", CR, LF, LF
byte "General Commands:", CR, LF
byte "V Set target I/O voltage (1.2V to 3.3V)", CR, LF
byte "I Display version information", CR, LF
Expand All @@ -2031,6 +2183,10 @@ MenuGPIO byte CR, LF, "GPIO Commands:", CR, LF
byte "C Read all channels (input, continuous)", CR, LF
byte "W Write all channels (output)", 0

MenuSWD byte CR, LF, "SWD Commands:", CR, LF
byte "I Identify SWD pinout (IDCODE Scan)", CR, LF
byte "C Set SWD clock frequency", 0

MenuShared byte CR, LF, LF, "General Commands:", CR, LF
byte "V Set target I/O voltage (1.2V to 3.3V)", CR, LF
byte "H Display available commands", CR, LF
Expand Down
100 changes: 100 additions & 0 deletions SWDCapture.spin
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
{
MIT License

Copyright (C) 2019 Adam Green (https://github.com/adamgreen)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
}
' SWDCapture - Module to capture SWDIO output on SWCLK rising edges.

CON
' Maximum number of longs to capture in m_buffer.
MAX_CAPTURE_LONGS = 4
MAX_CAPTURE_BITS = MAX_CAPTURE_LONGS * 32


VAR
' Pin configuration.
BYTE m_swclkPin
BYTE m_swdioPin

' Buffer to contain captured SWDIO output.
LONG m_buffer[MAX_CAPTURE_LONGS]

' Number of rising edges detected on SWCLK pin.
LONG m_clockCount

' Stack to be used by cog capturing data.
' UNDONE: Later check how much of this stack has actually been used.
LONG m_stack[128]

' Id of cog capturing data.
LONG m_cogId


PUB init
m_swclkPin~~
m_swdioPin~~
m_clockCount~
m_cogId~~

PUB start(swclkPin, swdioPin)
IF m_cogId <> -1
' It is an error to call start twice without an intervening stop call.
RETURN -1
m_swclkPin := swclkPin
m_swdioPin := swdioPin
m_clockCount~
LONGFILL(@m_buffer, 0, MAX_CAPTURE_LONGS)
RESULT := m_cogId := COGNEW(cogCode, @m_stack)
' Wait for a bit to make sure that cog is fully started before continuing.
WAITCNT(CLKFREQ / 10 + CNT)

PUB stop
IF m_cogId == -1
' Not running on cog so no need to cleanup
RETURN
COGSTOP(m_cogId)
m_cogId~~

PRI cogCode | clkMask, bit
' Make SWCLK & SWDIO pins act as input.
DIRA[m_swclkPin]~
DIRA[m_swdioPin]~

clkMask := |< m_swclkPin
REPEAT
' Wait for SWCLK falling edge.
WAITPEQ(0, clkMask, 0)

' Wait for SWCLK rising edge.
WAITPEQ(clkMask, clkMask, 0)
bit := INA[m_swdioPin]

' Store the bit just received if buffer not already full.
IF m_clockCount < MAX_CAPTURE_BITS
m_buffer[m_clockCount >> 5] |= bit << (m_clockCount & $1F)
m_clockCount++

PUB getCapturedBits(pBuffer)
' Wait a bit for the last bit to be captured.
WAITCNT(CLKFREQ / 10 + CNT)
LONGMOVE(pBuffer, @m_buffer, MAX_CAPTURE_LONGS)
RESULT := m_clockCount

Loading