I've designed Bus gofer to have easy access to the I2C and SPI busses from a computer. I use it mainly to program EEPROM and flash devices I use in other projects, but communication with any I2C/SPI device is possible. Because the MCU is quite powerful I felt it would be a waste to use it just for SPI and I2C access so I later added the PWM and ADC commands. The name comes from the bus pirate project since the area of application is similar.
The software was originally written for STM32F100 and communication with the MCU was done over USART1 with an external USB to serial converter (many available cheaply from eBay). After I implemented USB CDC in my STM32F10x library, the software was extended to STM32F103 and is now portable between both MCUs.
Although I've designed my own board for it (since I quite enjoy doing that), it can easily be used with any STM32F100 or STM32F103 development boards out there such as:
- LC Technology STM32F103C8 (tested, 5 euro on eBay, search for STM32F103)
- STM32VLDISCOVERY from ST (tested, available from big distributors like Farnell, Mouser, Digikey, etc.)
- Olimex STM32-H103 (untested)
- and others
I recommend a F103 board instead of F100 since a USB to serial converter is not needed and the communication is faster. Basically the only thing my custom board has and generic development boards lack is I2C pull-up resistors and the 1:4 resistor divider I've added to the ADC inputs to be able to measure automotive voltages (12V).
The device is driven by AT commands. Successful command execution returns OK, unsuccessful returns ERR.
|Echo on or off. On power up, echo is off.|
|ATI||Returns ID string.|
|AT?||Prints a list of supported AT commands.|
|AT+IPR=br||When using serial interface (F100) set the baud rate. Not available when using USB CDC for comm (F103). Baud rate must be a multiple of 9600.|
All interface write operations use data in the write buffer as a data source. All read operations store read data in the read buffer. Both buffers are 256 bytes.
|AT+BUFWR=dddddd...||Store data in the write buffer. Each byte should be represented by two hex digits, i.e. 08, 1A, F3, etc. without any separators.|
|AT+BUFRD||Display read buffer.|
|AT+BUFRDLEN||Display read buffer length.|
|AT+BUFSWAP||Swap read and write buffers.|
|AT+BUFCMP||Compare read and write buffers. The command will return ERR if either the lengths or the contents of the buffers don't match.|
|Turn automatic displaying of the read buffer after read commands on or off. On power up, BUFRRDDISP is 1.|
The buffer system is the core of how device comm is designed. Here are a few examples to make understanding it easier.
To write to a device:
- Use BUFWR to fill the write buffer.
- Use one of the write commands.
- Optionally, to verify, read the data back from the device (read buffer gets filled).
You can speed up the verify procedure by having the MCU do it for you. To do this, switch off the automatic read buffer displaying using BUFRDDISP=0. Step 3 will now read the device as before (read buffer gets filled), but the read buffer will not be displayed anymore. To compare if buffers match, issue BUFCMP as step 4.
To read from a device:
- Use one of the read commands.
- If BUFRDDISP is 1, the read buffer is displayed immediately.
- If BUFRDDISP is 0, the contents of the read buffer can be viewed by issuing BUFRD.
To quickly clone a device:
- Connect both source and destination device.
- If using I2C devices, configure them to use different I2C addresses.
- If using SPI devices, use one of the two available NSS pins for the source and the other for the destination device.
- Turn off read buffer displaying with BUFRDDISP=0.
- Select the source device. If using I2C, set the address of the source device using I2CADR=aa. If using SPI, select the NSS pin used by the device by issuing SPIDEV=n.
- Use one of the read commands to read data from the source device into the read buffer.
- Swap the read and write buffers using BUFSWAP.
- Select the destination device. If using I2C, set the address of the destination device using I2CADR=bb. If using SPI, select the NSS pin used by the device by issuing SPIDEV=k.
- Use one of the write commands to write data to the destinations device.
- Optionally verify the write.
- Repeat from step 5.
Generic I2C commands
|AT+I2CADR=aa||Set I2C address to hex address aa.|
|AT+I2CWR||Write data from the write buffer to I2C address set by I2CADR.|
|AT+I2CRD=len||Read decimal len bytes from I2C address set by I2CADR.|
Generic SPI commands
|AT+SPIRW||Writes data from the write buffer and fills the read buffer at the same time.|
|Select which one of the two NSS pins will be used.|
I2C EEPROM 24 commands
These are specialized functions for reading/writing to 24CXXX I2C EEPROM devices (developed for and tested on 24LC256).
|AT+EE24RD=aaaaaa,len||Read len bytes from hex address aaaaaa into the read buffer. Address must be exactly 6 characters long (24 bit).|
|AT+EE24WR=aaaaaa||Program write buffer to hex address aaaaaa.|
|AT+EE24MD5=len||Calculate MD5 from address 0 to len-1|
SPI flash 25 commands
These are specialized functions for reading/writing to XX25 SPI flash devices (developed for and tested on Spansion S25FL208).
|AT+FLS25RD=aaaaaa,len||Read len bytes from hex address aaaaaa into the read buffer. Address must be exactly 6 characters long (24 bit).|
|AT+FLS25WR=aaaaaa||Program write buffer to hex address aaaaaa.|
|AT+FLS25CE||Execute chip erase instruction. Note that the chip erase may take several seconds to complete (check part data sheet) and the device will not return OK until the erase cycle is complete.|
|AT+FLS25ID||Read 3 byte flash JEDEC ID.|
|AT+FLS25MD5=len||Calculate MD5 from address 0 to len-1|
SPI EEPROM 95 commands
These are specialized functions for reading/writing to XX95 SPI EEPROM devices (developed for and tested on ST95P08).
|AT+EE95RD=aaaaaa,len||Read len bytes from hex address aaaaaa into the read buffer. Address must be exactly 6 characters long (24 bit).|
|AT+EE95WR=aaaaaa||Program write buffer to hex address aaaaaa.|
|AT+EE95MD5=len||Calculate MD5 from address 0 to len-1|
You might have noticed that the specialized EEPROM / flash device commands include a MD5 sum command. This is to provide a secondary method of verification. Instead of reading back and verifying each write operation, the programming application can instead calculate the MD5 of programming data and ask the MCU to do the same calculation on the device data. If the MD5 sums match... The python script prg.py described below uses this approach.
Outputs a PWM signal on PB6 with selectable frequency and duty cycle.
|AT+PWMF=freq||Frequency in Hz. Min 1, max 1000000.|
|AT+PWMDC=percent||Duty cycle in percent. Min 1, max 99.|
Any combination of 16 channels of ADC1 on the device can be sampled.
|AT+ADCSTART=cccc,msec||Start acquiring on channels cccc (hex bitmask) with msec delay between samples. Min msec 1, max 10000.|
To sample channel 0, the corresponding bitmask would be 0001.
To sample channel 1, the corresponding bitmask would be 0002.
To sample channels 0 and 1, the corresponding bitmask would be 0003.
And so on...
The output format is compatible with the serial oscilloscope application by x-io technologies.
AT+ADCSTART=0003,200 2,17 2,17 2,18
Samples are in decimal format, all channels in a single line, lowest to highest channel, separated by commas. A new line means next sample values. A new line is printed every msec, in this case 200ms. Sample values are raw ADC values, lowest possible value is 0, highest is 4095 (12 bits).
Samples are output continuously until an uppercase X is received. During this time AT commands are not processed.
The device uses MCU's I2C_2 and SPI_1 interfaces.
To automate EEPROM/flash programming, I've written the following python script.
#!/usr/bin/python # bus gofer prg v1.0 by Matej Kogovsek, email@example.com, 20.jan.2014 # requires pySerial, http://pyserial.sourceforge.net # and intelhex, http://www.bialix.com/intelhex/ import sys,serial,datetime,hashlib,intelhex def atcmd(cmnd, resp, to = 0.2): if ser.timeout != to: ser.timeout = to ser.flushInput() ser.write(cmnd + '\n') r = ser.readline().rstrip(); if len(resp) > 0 and r.find(resp) == -1: if r == '': r = '(none)' raise Exception('Error! expected ' + resp + '\ncmnd was: ' + cmnd + '\nresp was: ' + r + '\n') return r if len(sys.argv) < 4: print 'usage: prg.py serial_if device_type filename' print print 'device types: ee24, ee95, fls25' sys.exit(1) devtype = sys.argv.upper() if devtype == 'EE24': pgw_size = 64 elif devtype == 'EE95': pgw_size = 16 elif devtype == 'FLS25': pgw_size = 256 else: print 'unknown device type' sys.exit(1) try: h = intelhex.IntelHex() if sys.argv[-3:] == 'hex': h.loadhex(sys.argv) else: h.loadbin(sys.argv) except intelhex.HexReaderError, e: print "Bad hex file\n", str(e) exit(1) ser = serial.Serial(sys.argv, 115200) try: for i in range(5): try: atcmd('AT+BUFRDDISP=0', 'OK') break except: pass if i == 4: print 'bus gofer not responding' sys.exit(1) if devtype == 'FLS25': jid = atcmd('AT+FLS25ID', '') if jid == 'FFFFFF' or jid == '000000': print 'flash 25 not detected' exit(1) print 'JEDEC ID:',jid print 'Erasing flash. Wait...' atcmd('AT+FLS25CE', 'OK', 30) if devtype == 'EE24': atcmd('AT+I2CADR=A0', 'OK') hna = h.minaddr() # veeeeeery slow function hma = h.maxaddr() # same a = hna sdt = datetime.datetime.now() tb = 0 md5 = hashlib.md5() while a < hma: s = '' adr = hex(a)[2:].zfill(6) nb = min(pgw_size, hma - a + 1) tb = tb + nb for i in range(nb): s = s + hex(h[a])[2:].zfill(2) a = a + 1 md5.update(s.decode('hex')) try: te = datetime.datetime.now() - sdt # time elapsed pd = 1.0 * (a-hna) / (hma-hna) # prg done tl = datetime.timedelta(seconds = int(((1.0-pd)/pd) * te.total_seconds())) # time left print adr,nb,tb,'/',hma-hna+1,'(',int(100.0*pd),'%)',tl,' left' atcmd('AT+BUFWR=' + s, 'OK') atcmd('AT+' + devtype + 'WR=' + adr, 'OK') atcmd('AT+' + devtype + 'RD=' + adr + ',' + str(i + 1), 'OK') atcmd('AT+BUFCMP', 'OK') except Exception, e: print e break print if hna == 0: print 'Device MD5:',atcmd('AT+' + devtype + 'MD5=' + str(hma+1), '', 20) print 'File MD5 :',md5.hexdigest().upper() finally: ser.close() print 'Done.'
Bill of materials
|Qty||Value / Farnell code||Device||Size||Parts|
|3||10k||resistor||0805||R8, R9, R12|
|3||100n||multilayer||0805||C3, C9, C10|
|1||USB B con.||X1|
The precompiled hex assumes an 8MHz crystal.
To compile the sources, my STM32F10x library is required.