понедельник, 28 января 2013 г.

Цифровые датчики SmartBricks: не NXT-G единым...

Тому, что на блоге не было давно статей с детальным обзором датчиков SmartBricks, есть одно объяснение - предыдущие обзоры рассматривали аналоговые датчики, которые по поведению ничем ни отличались от уже существующих LEGO датчиков, поэтому и при их программировании в тестах применялись те же самые средства, что и для обычных датчиков.
Для новых цифровых датчиков в языках Python и NXC, использовавшихся для обработки данных в тестах, нет готовых библиотек для опроса SmartBricks датчиков. Поэтому, прежде чем проводить эксперименты с новым датчиком, нужно было разобраться с протоколами связи с датчиками и реализовать соответствующий функционал в языках программирования.

Следует отметить, что для среды программирования NXT-G, компания SmartBricks предлагает для загрузки уже готовые блоки, а также и примеры их использования. Но по причине неудобства использования этой среды в целом для тестирования - это больше касается возможностей взаимодействия с компьютером, нежели самого процесса программирования и производительности, было решено не использовать NXT-G для подготовки обзоров.

Прежде, чем перейти описанию того как работать с цифровыми датчиками в языках NXC и Python, нужно сначала обратить внимание на, то как устроен интерфейс общения NXT блока с датчиком через протокол I2C. В целом, неплохая вводная есть известной книге "Extreme NXT: Extending the LEGO MINDSTORMS NXT to the Next Level" (*) и есть надежда, что это описание перекочует в "перевод" на русский язык, который делается энтузиастами из Оренбурга. Сейчас же, в двух словах можно сказать, что цифровой датчик по сути представляет из себя отдельное вычислительное устройство, которое может отдавать данные - обработанные результаты измерений, причем обработка измерений может контролироваться снаружи - на устройство могут подаваться входные данные, задающие правила вычислений, или переменные, участвующие в этих вычислениях. Иными словами, NXT блок может передавать в датчик данные по I2C шине и считывать из датчика данные.

Чтобы не спутать такое вычислительное устройство с другим, помимо порта подключения к NXT блоку, у него есть свой идентификатор, называемый также адрес. Адрес будет одним и тем же для двух одинаковых датчиков.

Чтение и запись данных происходит через регистры - специальные области памяти, поэтому каждый регистр имеет свой адрес. Разные устройства-датчики могут иметь одни и те же адреса для работы c регистрами. Они не похожи на обычные регистры процессоров хотя бы по той причине, что можно за одну операцию записи или чтения можно обратиться сразу к нескольким регистрам, указав первый из них, а количество данных считываемых из устройства - длиной в несколько регистров-ячеек.

Например, есть два регистра с адресами 0x42 и 0x44 (адреса по привычке обозначаются шестнадцатеричными числами) и каждый из них адресует по два байта информации. В итоге, оба регистра можно считать за одну операцию, указав адрес 0x42 и количество необходимых данных в 4 байта.

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

Теперь стоит перейти к конкретике.
На текущий момент компания SmartBricks выпускает три цифровых датчика:

ДатчикI2C адрес
Датчик магнитного поля
0x48
Приемник сигналов ДУ
0x50
Датчик линии
0x54

Каждый из них поддерживает разное количество регистров для чтения данных и передачи управляющей информации.

Язык программирования NXC предоставляет множество функций для работы с I2C устройствами. Наиболее часто используемые из них:

I2CBytes
Выполняет транзакции  чтения/записи.
В качестве входного параметра, помимо порта датчика, принимает массив данных, где должен быть указан адрес устройства, адрес регистра для записи или чтения данных, данные для записи (если нужно). Другим параметров является количество данных, сколько ожидается в буфере для чтения.
ReadI2CRegister
Чтение байта данных из конкретного регистра.
Входные данные: адрес устройства, адрес регистра.
WriteI2CRegister
Запись байта данных в конкретный регистр
Входные данные: адрес устройства, адрес регистра.
I2CCheckStatus
Проверка состояния шины данных для определенного порта

Как результат, для простейшей работы с датчиком линии можно составить следующую библиотеку функций:
#define I2CAddr_SBLine          0x54
#define regSBLineMode           0x40
#define regSBLineIndicator      0x41
#define regSBLineSteering       0x42
#define regSBLineSteeringPID    0x43
#define regSBLineBinary         0x44

sub SetSensorSBLine(const byte port) {
    SetSensorLowspeed(port);
}

sub SetSensorSBLineIndicatorValue(const byte port, byte value) {
    while (I2CCheckStatus(port) ==  STAT_COMM_PENDING);
    WriteI2CRegister(port, I2CAddr_SBLine, regSBLineMode, 1);
    while (I2CCheckStatus(port) ==  STAT_COMM_PENDING);
    WriteI2CRegister(port, I2CAddr_SBLine, regSBLineIndicator, value);
}

char SensorSBLineValue(const byte port) {
    char value;

    while (I2CCheckStatus(port) ==  STAT_COMM_PENDING);
    WriteI2CRegister(port, I2CAddr_SBLine, regSBLineMode, 0);
    while (I2CCheckStatus(port) ==  STAT_COMM_PENDING);
    ReadI2CRegister(port, I2CAddr_SBLine, regSBLineSteering, value);

    return value;
}

byte SensorSBLineValueBin(const byte port) {
    byte value;

    while (I2CCheckStatus(port) ==  STAT_COMM_PENDING);
    WriteI2CRegister(port, I2CAddr_SBLine, regSBLineMode, 0);
    while (I2CCheckStatus(port) ==  STAT_COMM_PENDING);
    ReadI2CRegister(port, I2CAddr_SBLine, regSBLineBinary, value);
    
    return value;
}

Пример, ниже поможет получить представление, как работать с этими функциями:
task main() {
    SetSensorSBLine(S1);
    
    byte val;

    while(! ButtonPressed(BTNCENTER)) {
        val = SensorSBLineValueBin(S1);
        for(int i=0; i<8; i++) {
            NumOut(i*6, LCD_LINE1, (val >> i) & 0x01);
        }
        Wait(500);
    }
    
    Wait(1000);

    while(! ButtonPressed(BTNCENTER)) {
        ClearScreen();
        NumOut(0, LCD_LINE1, SensorSBLineValue(S1));
        Wait(500);
    }

    Wait(1000);

    byte i = 1;
    while(! ButtonPressed(BTNCENTER)) {
        SetSensorSBLineIndicatorValue(S1, i);
        i = i == 128 ? 1 : i * 2;
        Wait(500);
    }
}

Поскольку все модули языка Python по сути - открытый исходный код, то в них без труда можно найти то, как сейчас реализована не только работа с ультразвуковым датчиком расстояния, который единственный цифровой из стандартных датчиков LEGO, но и с датчиками компаний HiTechnic и Mindsensors. Используя эти примеры, можно без особого труда подготовить модуль для работы с датчиками SmartBricks:
from .common import *
from .digital import BaseDigitalSensor
 
class Line(BaseDigitalSensor):
    """SMARTBRICKS Line sensor."""
    I2C_ADDRESS = BaseDigitalSensor.I2C_ADDRESS.copy()
    I2C_ADDRESS.update({
        'Command': (0x40, 'B'),
        'Indicator': (0x41, 'B'),
        'Steering': (0x42, 'b'),
        'SteeringPID': (0x43, 'b'),
        'Binary': (0x44, 'B'),
        'S1': (0x50, 'B'),
        'S2': (0x51, 'B'),
        'S3': (0x52, 'B'),
        'S4': (0x53, 'B'),
        'S5': (0x54, 'B'),
        'S6': (0x55, 'B'),
        'S7': (0x56, 'B'),
        'S8': (0x57, 'B'),
        'all_sensors': (0x50, '8B'),
    })
    I2C_DEV = 0x54
 
    class Commands:
        NORMAL = 0x00
        INDICATOR = 0x01
        CALIBRATE_BLACK = 0x02
        CALIBRATE_WHITE = 0x03
        CALIBRATE = 0x04
        RESET_CALIBRATION = 0x05
        RAW = 0x06
        PID_CONFIG = 0x07
 
    class CommandsMods:
        COMPENSATION_ON = 0x80
        PROFILE_INSIDE = 0x40
        INVERSE = 0x20
 
    def __init__(self, brick, port, check_compatible=False):
        super(Line, self).__init__(brick, port, check_compatible)
 
    def set_mode(self, mode):
        self.write_value('Command', (mode, ))
 
    def set_indicator(self, value):
        self.set_mode(self.Commands.INDICATOR)
        self.write_value('Indicator', (value & 0xff, ))
 
    def get_binary_value(self):
        self.set_mode(self.Commands.NORMAL)
        return self.read_value('Binary')[0]
 
    def get_scaled_value(self):
        self.set_mode(self.Commands.NORMAL)
        return self.read_value('Steering')[0]
 
    get_sample = get_scaled_value
 
Line.add_compatible_sensor(None, 'SMBRCKS', 'LINE')

Полученный модуль нужно положить в каталог, куда установлен модуль nxt-python - Python27\Lib\site-packages\nxt\sensor (для Windows). В этом же каталоге находится файл __init__.py, в котором нужно добавить строки, для более удобного доступа к датчикам:
import smartbricks
SBLine = smartbricks.Line

Теперь, из Python-скриптов с датчиком SmartBricks, в данном случае с датчиком линии, можно будет работать следующим образом:
from nxt.locator import find_one_brick
from nxt.sensor import *
from time import sleep
 
br=find_one_brick(debug=True)
 
sbline = SBLine(br, PORT_1)
 
for i in (1,2,4,8,16,32,64,128):
    print sbline.set_indicator(i)
    sleep(0.5)
 
i = 0
while(i<10):
    print bin(sbline.get_binary_value())
    i = i+1
    sleep(1)
 
i = 0
while(i<10):
    print sbline.get_sample()
    i = i+1
    sleep(1)
 

Комментариев нет:

Отправить комментарий

Related Posts Plugin for WordPress, Blogger...