Связь с STM32VLDiscovery по USB

Все знают, что в STM32F100RB USB не поддерживается, что создаёт определенные неудобства.
Однажды мне стало совсем лениво в очередной раз цеплять к плате USB — TTL Serial переходник
и я задумался, нельзя ли использовать для связи встроенный STLINK и передавать данные по SWD.
Первоначальная идея была такая: Контроллер пишет данные в какую-то область памяти, а утилита st-link_cli с ключем -r8 эту область читает. попробовал вручную — получилось. Сделал программку, которая запускает st-link_cli как дочерний процесс, считывает байты и выводит на экран. Работало жутко медленно.
В качестве второй итерации приспособил программку STLinkDownload, портировав её под Windows. Для контроллера написал класс SWDSerial для libmaple:
#include <string.h>
#include "wirish.h"

/**
 * The buffer is empty when head == tail.
 * The buffer is full when the head is one byte in front of the tail,
 * modulo buffer length.
 * One byte is left free to distinguish empty from full. */

#define SWD_TX_BUF_SIZE               64
#define SWD_RX_BUF_SIZE               16

/** SWD device data structure */
typedef struct swd_dev {
    uint32 tx_head;         /**< Index of the next item to remove */
    uint32 tx_tail;         /**< Index where the next item will get inserted */
    uint32 tx_size;         /**< Buffer capacity minus one */
    uint32 rx_head;         /**< Index of the next item to remove */
    uint32 rx_tail;         /**< Index where the next item will get inserted */
    uint32 rx_size;         /**< Buffer capacity minus one */
    uint8  tx_buf[SWD_TX_BUF_SIZE];   /**< Actual TX buffer used by rb */
    uint32  rx_buf[SWD_RX_BUF_SIZE];   /**< Actual RX buffer used by rb */
} swd_dev;


static inline void swd_rb_init(swd_dev *swd, uint16 tx_size, uint16 rx_size) {
    swd->tx_head = 0;
    swd->tx_tail = 0;
    swd->tx_size = tx_size - 1;
    swd->rx_head = 0;
    swd->rx_tail = 0;
    swd->rx_size = rx_size - 1;
}

static inline int swd_rb_is_full(swd_dev *swd) {
    return (swd->tx_tail + 1 == swd->tx_head) ||
        (swd->tx_tail == swd->tx_size && swd->tx_head == 0);
}

static inline void swd_rb_insert(swd_dev *swd, uint8 element) {
    swd->tx_buf[swd->tx_tail] = element;
    swd->tx_tail = (swd->tx_tail == swd->tx_size) ? 0 : swd->tx_tail + 1;
}

static inline int swd_rb_safe_insert(swd_dev *swd, uint8 element) {
    if (swd_rb_is_full(swd)) {
        return 0;
    }
    swd_rb_insert(swd, element);
    return 1;
}

static inline int swd_rb_is_empty(swd_dev *swd) {
    return swd->rx_head == swd->rx_tail;
}

static inline uint8 swd_rb_remove(swd_dev *swd) {
    uint8 ch = (uint8)swd->rx_buf[swd->rx_head];
    swd->rx_head = (swd->rx_head == swd->rx_size) ? 0 : swd->rx_head + 1;
    return ch;
}

/**
 * Attempt to remove the first item from a ring buffer.
 * If the ring buffer is nonempty, removes and returns its first item.
 * If it is empty, does nothing and returns a negative value.
 */
static inline int16 swd_rb_safe_remove(swd_dev *swd) {
    return swd_rb_is_empty(swd) ? -1 : swd_rb_remove(swd);
}


#define SWD_TIMEOUT 3000


swd_dev swd;

volatile uint32 *DCRDR = (uint32 *)0xe000edf8;

class SWDSerial : public Print {
public:
    SWDSerial(void);

    uint32 available(void);

    uint32 read(void *buf, uint32 len);
    uint8  read(void);

    void write(uint8);
    void write(const char *str);
    void write(const void*, uint32);
};

extern SWDSerial SerialSWD;


uint32 SWDSendBytes(uint8 *buf, uint32 len)
{
uint16 i;
    if (!buf)
        return 0;
    if (len > swd.tx_size)
        len = swd.tx_size;
    for(i=0;i<len;i++)
	if(!swd_rb_safe_insert(&swd, buf[i])) 
	    break;
    return i;
}

uint32 SWDReceiveBytes( uint8 *buf, uint32 len)
{
uint16 i;
int16 ch;
    if (!buf)
        return 0;
    if (len > 31)
        len = 31;
    for(i=0;i<len;i++) {
        if((ch = swd_rb_safe_remove(&swd)) < 0) 
           break;
        else
           buf[i] = (uint8)ch;
    }
    return i;
}

SWDSerial::SWDSerial(void) {
    swd_rb_init(&swd, SWD_TX_BUF_SIZE, SWD_RX_BUF_SIZE);
    *DCRDR = (uint32)&swd;
}

void SWDSerial::write(uint8 ch) {
    const uint8 buf[] = {ch};
    this->write(buf, 1);
}

void SWDSerial::write(const char *str) {
    this->write(str, strlen(str));
}

void SWDSerial::write(const void *buf, uint32 len) {
    if (!buf) {
        return;
    }

    uint32 txed = 0;
    uint32 old_txed = 0;
    uint32 start = millis();

    while (txed < len && (millis() - start < SWD_TIMEOUT)) {
        txed += SWDSendBytes((uint8*)buf + txed, len - txed);
        if (old_txed != txed) {
            start = millis();
        }
        old_txed = txed;
    }
}

uint32 SWDSerial::available(void) {
    return 0;
}

uint32 SWDSerial::read(void *buf, uint32 len) {
    if (!buf) {
        return 0;
    }

    uint32 rxed = 0;
    while (rxed < len) {
        rxed += SWDReceiveBytes((uint8*)buf + rxed, len - rxed);
    }

    return rxed;
}

/* Blocks forever until 1 byte is received */
uint8 SWDSerial::read(void) {
    uint8 buf[1];
    this->read(buf, 1);
    return buf[0];
}


Обмен данными идет через два кольцевых буфера, это сильно ускоряет процесс, так как Print в libmaple выводит цифры и буквы по одной.
Для передачи на хост ссылки на адрес буфера используется регистр DCRDR.
Программу обмена можно скачать здесь, скомпилированная рядом.

В качестве примера использования программа чтения DS18B20:

/* DS18S20 Temperature chip i/o  */

OneWire  ds(PC13);  
SWDSerial SerialSWD;

void setup(void) {
}



void loop(void) {
  byte i;
  byte present = 0;
  byte data[12];
  byte addr[8];
  
  if ( !ds.search(addr)) {
      SerialSWD.print("No more addresses.\n");
      ds.reset_search();
      return;
  }
  
  SerialSWD.print("R=");
  for( i = 0; i < 8; i++) {
    SerialSWD.print(addr[i], HEX);
    SerialSWD.print(" ");
  }

  if ( OneWire::crc8( addr, 7) != addr[7]) {
      SerialSWD.print("CRC is not valid!\n");
      return;
  }
  
  if ( addr[0] != 0x28) {
      SerialSWD.print("Device is not a DS18B20 family device.\n");
      return;
  }

  ds.reset();
  ds.select(addr);
  ds.write(0x44,1);         // start conversion, with parasite power on at the end
  
  delay(1000);     // maybe 750ms is enough, maybe not
  // we might do a ds.depower() here, but the reset will take care of it.
  
  present = ds.reset();
  ds.select(addr);    
  ds.write(0xBE);         // Read Scratchpad

  for ( i = 0; i < 9; i++) {           // we need 9 bytes
    data[i] = ds.read();
  }
  int T = (((data[1]) << 8) + data[0])/2; 
  SerialSWD.print(" T = "); SerialSWD.print(T);  SerialSWD.println();
}

// Force init to be called *first*, i.e. before static object allocation.
// Otherwise, statically allocated objects that need libmaple may fail.
__attribute__((constructor)) void premain() {
    init();
}

int main(void) {
    setup();

    while (true) {
        loop();
    }

    return 0;
}

Программа использует ардуиновскую библиотеку OneWire, из примера которой и скопированна чуть менее, чем полностью. Библиотека также подпилена минимально, в функциях OneWire::write_bit и OneWire::read_bit заменены макросы прямого доступа к портами. Хотя, в принципе, работает и с digitalWrite()/digitalRead().

Комментарии (16)

RSS свернуть / развернуть
Изящное решение!

(Жаль стандартного+штатного нет… На ST-link-овском разъеме есть линии RX и TX, вот были бы они всамделишным RS-232… Так ведь нет...)
0
На ST-link-овском разъеме есть линии RX и TX, вот были бы они всамделишным RS-232… Так ведь нет...)
Так для чего это может тебе понадобиться?
0
А чем сенмихост не приглянулся?
0
Что?
0
Semihosting. Не станите же вы утверждать то у stm32 нет Cortex'ового отладочного порта. Поищите в доках на среду разработки упоминание «semi», малоли.
0
А, понятно. Есть, конечно. Но под него нужна среда, плюс сложно навесить поверх свой функционал. А тут всё в исходниках, в принципе, можно даже написать драйвер, эмулирующий последовательный порт и сношаться в платой совершенно прозрачно.
0
не подсажите как библиотеку пилили?
0
я про OneWire)))
0
Просто:
uint8_t OneWire::reset() {
    uint8_t r;
    uint8_t retries = 125;

    // wait until the wire is high... just in case
    pinMode(pin,INPUT);
    do {
	if ( retries-- == 0) return 0;
	delayMicroseconds(2); 
    } while( !digitalRead( pin));
    
    digitalWrite(pin,0);   // pull low for 500uS
    pinMode(pin,OUTPUT);
    delayMicroseconds(500);
    pinMode(pin,INPUT);
    delayMicroseconds(65);
    r = !digitalRead(pin);
    delayMicroseconds(490);
    return r;
}

void OneWire::write_bit(uint8_t v) {
    static uint8_t lowTime[] = { 55, 5 };
    static uint8_t highTime[] = { 5, 55};
    
    v = (v&1);
    pinMode(pin,OUTPUT);  // make pin an output, do first since we expect to be at 1
    digitalWrite(pin,0);  // zero
    delayMicroseconds(lowTime[v]);
    digitalWrite(pin,1);  // one, push pin up - important for
                           // parasites, they might start in here
    delayMicroseconds(highTime[v]);
}

uint8_t OneWire::read_bit() {
    uint8_t r;
    
    pinMode(pin,OUTPUT);   // make pin an output, do first since we expect to be at 1
    digitalWrite(pin,0);   // zero
    delayMicroseconds(1);
    pinMode(pin,INPUT);         // let pin float, pull up will raise
    delayMicroseconds(5);          // A "read slot" is when 1mcs > t > 2mcs
    r = ( digitalRead( pin)) ? 1 : 0;   // check the bit
    delayMicroseconds(50);        // whole bit slot is 60-120uS, need to give some time
    
    return r;
}

void OneWire::write(uint8_t v, uint8_t power) {
    uint8_t bitMask;
    
    for (bitMask = 0x01; bitMask; bitMask <<= 1) {
	OneWire::write_bit( (bitMask & v)?1:0);
    }
    if ( !power) {
	pinMode(pin,INPUT);
	digitalWrite(pin,0);
    }
}
0
о спасибо
0
from libraries/OneWire/OneWire.h:10,
                 from libraries/OneWire/OneWire.cpp:99:
./wirish/comm/HardwareSPI.h:62: warning: #warning "Unexpected clock speed; SPI frequency calculation will be incorrect"
In file included from libraries/OneWire/OneWire.cpp:99:
libraries/OneWire/OneWire.h:78: error: #error "Please define I/O register types here"
libraries/OneWire/OneWire.h:85: error: 'IO_REG_TYPE' does not name a type
libraries/OneWire/OneWire.h:86: error: ISO C++ forbids declaration of 'IO_REG_TYPE' with no type
libraries/OneWire/OneWire.h:86: error: expected ';' before '*' token
libraries/OneWire/OneWire.cpp: In constructor 'OneWire::OneWire(uint8_t)':
libraries/OneWire/OneWire.cpp:105: error: 'bitmask' was not declared in this scope
libraries/OneWire/OneWire.cpp:105: error: 'PIN_TO_BITMASK' was not declared in this scope
libraries/OneWire/OneWire.cpp:106: error: 'baseReg' was not declared in this scope
libraries/OneWire/OneWire.cpp:106: error: 'PIN_TO_BASEREG' was not declared in this scope
libraries/OneWire/OneWire.cpp: In member function 'uint8_t OneWire::reset()':
libraries/OneWire/OneWire.cpp:124: error: 'pin' was not declared in this scope
libraries/OneWire/OneWire.cpp: In member function 'void OneWire::write_bit(uint8_t)':
libraries/OneWire/OneWire.cpp:145: error: 'pin' was not declared in this scope
libraries/OneWire/OneWire.cpp: In member function 'uint8_t OneWire::read_bit()':
libraries/OneWire/OneWire.cpp:156: error: 'pin' was not declared in this scope
libraries/OneWire/OneWire.cpp: In member function 'void OneWire::write(uint8_t, uint8_t)':
libraries/OneWire/OneWire.cpp:174: error: 'pin' was not declared in this scope
libraries/OneWire/OneWire.cpp: In member function 'void OneWire::depower()':
libraries/OneWire/OneWire.cpp:201: error: 'baseReg' was not declared in this scope
libraries/OneWire/OneWire.cpp:201: error: 'bitmask' was not declared in this scope
libraries/OneWire/OneWire.cpp:201: error: 'DIRECT_MODE_INPUT' was not declared in this scope
libraries/OneWire/OneWire.cpp: At global scope:
libraries/OneWire/OneWire.cpp:368: error: expected initializer before 'dscrc_table'
libraries/OneWire/OneWire.cpp: In static member function 'static uint8_t OneWire::crc8(uint8_t*, uint8_t)':
libraries/OneWire/OneWire.cpp:398: error: 'dscrc_table' was not declared in this scope
libraries/OneWire/OneWire.cpp:398: error: 'pgm_read_byte' was not declared in this scope
make: *** [build/./libraries/OneWire/OneWire.o] Помилка 1

вот такая фигня
0
#include «wirish.h»
есть в начале?
0
да конечно.
0
понятно. у меня слегка другая версия была.
строчки с bitmask и baseReg тупо закомментируйте.
0
ок. попробую. было бы не плохо если бы вы своим вариантом поделились.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.