Как изменить этот код для имитации двоичного кодировщика?

Я обнаружил следующий подчиненный код SPI на этом сайте:

#include <SPI.h>

char buf [100];
volatile byte pos;
volatile bool process_it;

void setup (void)
{
  Serial.begin (115200);   // отладка

  // включаем SPI в ведомом режиме
  SPCR |= bit (SPE);

  // нужно отправить на вход master, *slave на выход*
  pinMode (MISO, OUTPUT);

  // готовимся к прерыванию
  pos = 0;   // буфер пустой
  process_it = false;

  // теперь включаем прерывания
  SPI.attachInterrupt();

}  // конец настройки


// Процедура прерывания SPI
ISR (SPI_STC_vect)
{
byte c = SPDR;  // получаем байт из регистра данных SPI

  // добавить в буфер, если есть место
  if (pos < sizeof buf)
    {
    buf [pos++] = c;

    // пример: новая строка означает время обработки буфера
    if (c == '\n')
      process_it = true;

    }  // конец комнаты доступен
}  // конец процедуры прерывания SPI_STC_vect

// основной цикл - ожидание установки флага в процедуре прерывания
void loop (void)
{
  if (process_it)
    {
    buf [pos] = 0;
    Serial.println (buf);
    pos = 0;
    process_it = false;
    }  // конец установленного флага

}  // конец цикла

Как изменить приведенный выше код, чтобы он реагировал на входной тактовый сигнал точно так же, как в этом протокол SSI? Можно использовать Uno или Nano.

По сути, я хочу, чтобы Arduino работала как абсолютный энкодер, чтобы он передавал 13-битные двоичные данные (это могут быть любые случайные фиксированные двоичные данные), как показано в приведенном выше протоколе SSI. Таким образом, Arduino будет выводить 13-битные двоичные данные вместе со стартовым битом и tm, принятыми во внимание, как показано в документе выше, без четности.

Основной код, который я планирую использовать:

const int CLOCK_PIN = 2;
const int DATA_PIN = 3;
const int BIT_COUNT = 16;  
void setup() {
    pinMode(DATA_PIN, INPUT);
    pinMode(CLOCK_PIN, OUTPUT);
    digitalWrite(CLOCK_PIN, HIGH);
    Serial.begin(115200);
  }
void loop() {
    float reading = readPosition();
    Serial.println(reading,2);
    delay(25);
  }

//читаем текущую угловую позицию
float readPosition() {
 unsigned long graysample = shiftIn(DATA_PIN, CLOCK_PIN, BIT_COUNT);
 delayMicroseconds(100); // Часы должны быть высокими в течение 20 микросекунд, прежде чем можно будет взять новую выборку
unsigned long binarysample = grayToBinary32(graysample);
 return ((binarysample * 360UL) / 65536.0); // выходное значение от 0 до 360 с точностью до двух точек
}
//читаем байт данных с цифрового входа платы.
unsigned long shiftIn(const int data_pin, const int clock_pin, const int bit_count) {
 unsigned long data = 0;
 for (int i=0; i<bit_count; i++) {
 data <<= 1; // сдвигаем все считанные данные влево на один бит.

 // цифровая запись (clock_pin, LOW);
 PORTD &= ~(1 << 5); // тактовый вывод переходит в низкий уровень
 delayMicroseconds(1);
 // цифровая запись (clock_pin, HIGH);
 PORTD |= (1 << 5); // стопорный штифт становится высоким
 delayMicroseconds(1);
 data |= digitalRead(data_pin); // привязать новый бит чтения ко всем прочитанным данным.
 }
 return data;
}
unsigned int grayToBinary32(unsigned int num)
{
 num = num ^ (num >> 16);
 num = num ^ (num >> 8);
 num = num ^ (num >> 4);
 num = num ^ (num >> 2);
 num = num ^ (num >> 1);
 return num;
}

, 👍0

Обсуждение

Что мешает вам сделать это с помощью digitalWrite() и delayMicroseconds()?, @Edgar Bonet

@EdgarBonet Для SSI требуется минимум 100 кГц. Также я не в теме, мне это нужно, потому что я хочу проверить основную библиотеку, которая может читать входящий код Грея из кодировщика. Но энкодер слишком дорог, поэтому я хотел имитировать раб., @floppy380

@EdgarBonet Буду признателен за ваш ответ, но если у меня нет времени или интереса, я хочу спросить вас, делаю ли я это с помощью DigitalWrite() и delayMicroseconds(); Мне также нужно, чтобы ведомое устройство работало с часами, как показано в документе SSI. Так что это кажется немного сложнее, чем я думал., @floppy380

Это жесткий протокол для битбанга. Вы не сделаете этого с SPI, так как там есть вещи, которые SPI не может сделать. Так что забудьте SPI. Вы бы сделали это только с прерыванием для часов и очень быстрым и компактным кодом (что, вероятно, означает язык ассемблера). Лично я бы реализовал это в CPLD, а не в MCU., @Majenko

@Majenko Как насчет Arduino в качестве мастера и использования SPI? Я видел, как некоторые используют на некоторых форумах, таких как https://forum.arduino.cc/index.php?topic=156812.0 и http://www.jgelectronics.nl/art-net-dmx/arduino-project-4.html . По крайней мере, я могу купить дешевый энкодер в качестве ведомого, но мне нужно, чтобы мастер работал хотя бы, @floppy380


1 ответ


Лучший ответ:

0

Вы можете попытаться взломать протокол, используя прямой доступ к порту. Этот менее удобен, но намного быстрее, чем digitalWrite(). Ниже приведен например, непроверенная программа. Я предполагаю, что распиновка

  • данные = PB0 = цифровой 8
  • часы = PB1 = цифра 9
  • контакты PB2–PB5 (10–13) не используются

Изменить: эта обновленная программа использует часы в качестве входных данных, а данные как выход. Предыдущая версия использовала обе линии в качестве выходов, что было из-за моего непонимания.

#include <avr/io.h>

int main(void)
{
    uint16_t data = 0x155;

    DDRB  |= _BV(PB0);
    PORTB |= _BV(PB0);
    TCCR1B = _BV(CS10);  // запуск таймера 1 @ F_CPU
    loop_until_bit_is_clear(PINB, PB1);

    for (;;) {
        loop_until_bit_is_set(PINB, PB1);
        PORTB = data & 1;
        data >>= 1;
        TCNT1 = -304;        // 20 мкс * 16 МГц - 16 циклов
        TIFR1 |= _BV(TOV1);  // очистить флаг переполнения
        while (bit_is_set(PINB, PB1)) {
            if (bit_is_set(TIFR1, TOV1)) {  // таймер переполнен
                PORTB = 1;  // высокий уровень данных
                data = 0x155;
                loop_until_bit_is_clear(PINB, PB1);
                break;
            }
        }
    }
}

Это должно быть в состоянии работать на частотах примерно до 200–250 кГц. Задержка составляет около 0,5 мкс. между нарастающими фронтами часов и данными, которые были отправлены, с дрожание 3 тактовых циклов.

Слово данных отправляется первым младшим значащим битом, в отличие от показана схема протокола. Однако, если вы отправляете произвольные данные, это должно не проблема.

Обратите внимание, что это чистая программа avr-libc, которая не использует Ядро Ардуино. Он использует опрос для проверки входов и таймера. прерывания никогда не разрешаются.

,

Большое спасибо! Я попробую это с мастер-кодом, который у меня есть. Один ардуино будет ведомым, а другой будет ведущим. Ваш код имитирует ведомого, если я не ошибаюсь, но есть одна вещь, которую я немного запутал в вашем коде. В этом протоколе ведомое устройство должно принимать тактовый сигнал. Но в вашем коде, когда я проверяю цифровой 9-контактный вывод с помощью осциллографа, он выводит битовый поток, очень похожий на цифровой 8. Я думал, что ведомый будет получать только тактовые сигналы в качестве входных данных от ведущего, но в вашем случае ведомый, кажется, выводит битовый поток. Я здесь что-то не так?, @floppy380

По поводу «_В этом протоколе ведомое устройство должно получать тактовый сигнал_»: О, понятно, я не знал об этом., @Edgar Bonet

Обратите внимание, что в конце моего вопроса я добавил мастер-код, который я нашел для 16-битного кодировщика. Код написан для мастера и преобразует код Грея в двоичный. (Обычно энкодеры abs выводят код Грея для большей эффективности.) Но для ведомой стороны предполагается получение тактового сигнала таким образом, что каждый тактовый сигнал выводит один бит. Более подробная информация об этом протоколе здесь: https://www.posital.com/media/posital_media/documents/AbsoluteEncoders_Context_Technology_SSI_AppNote.pdf Моя цель — попробовать этот основной код с имитацией подчиненного устройства. Ведомый может отправлять данные любого угла. Лучше быть постоянными данными, чтобы проверить, @floppy380

Это отлично работает! Мне просто нужно добавить мастер-код после возврата //digitalWrite(clock_pin,LOW); ПОРТД &= ~(1 << 5); // тактовый вывод переходит в низкий уровень задержка в микросекундах (1); // цифровая запись (clock_pin, HIGH); ПОРТ |= (1 << 5); // стопорный штифт становится высоким задержка в микросекундах (1); чтобы точно воспроизвести требуемый рисунок. Я проверил, теперь работает отлично, спасибо,, @floppy380