Чтение одного регистра
Пора применить всю эту теорию на практике!
Прежде всего нужно узнать адреса target-устройств акселерометра и магнитометра внутри чипа. Их можно найти в даташите LSM303AGR на странице 39. Они такие:
- 0011001 — для акселерометра
- 0011110 — для магнитометра
ПРИМЕЧАНИЕ Помните, что это только 7 старших битов адреса: восьмой бит определяет, выполняем мы чтение или запись.
Дальше нужен регистр, из которого будем читать. Многие I2C-чипы предоставляют регистр идентификации устройства, который контроллер может прочитать. Учитывая тысячи (а то и миллионы) I2C-чипов, весьма вероятно, что рано или поздно будут выпущены два чипа с одинаковым адресом (ведь адрес — «всего лишь» 7 бит). С помощью регистра идентификации драйвер может убедиться, что он действительно общается с LSM303AGR, а не с каким-то другим чипом, который случайно имеет тот же адрес. Как сказано в даташите LSM303AGR (в частности, на страницах 46 и 61), у этого устройства есть два таких регистра: WHO_AM_I_A по адресу 0x0f и WHO_AM_I_M по адресу 0x4f. В них записаны битовые шаблоны, уникальные для этого устройства. («A» — от «Accelerometer», акселерометр, а «M» — от «Magnetometer», магнитометр.)
Осталась только программная часть: нужно понять, каким API microbit или HAL-крейта воспользоваться. Если прочитать спецификацию продукта nRF52833, вы скоро обнаружите, что отдельной периферии именно для I2C там нет. Вместо неё есть более универсальные периферийные блоки, совместимые с I2C, которые называются TWI («Two-Wire Interface», двухпроводный интерфейс), TWIM («Two-Wire Interface Master») и TWIS («Two-Wire Interface Slave»). Обычно мы будем работать в режиме контроллера и воспользуемся более новым TWIM, который поддерживает «Easy DMA»; TWI оставлен в основном для совместимости со старыми устройствами.
Теперь, если сопоставить документацию модуля twi(m) из крейта microbit со всей остальной собранной информацией, получится вот такой код, который читает и выводит оба идентификатора устройств (examples/chip-id.rs):
#![deny(unsafe_code)]
#![no_main]
#![no_std]
use cortex_m::asm::wfi;
use cortex_m_rt::entry;
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use embedded_hal::i2c::I2c;
use microbit::{hal::twim, pac::twim0::frequency::FREQUENCY_A};
const ACCELEROMETER_ADDR: u8 = 0b0011001;
const MAGNETOMETER_ADDR: u8 = 0b0011110;
const ACCELEROMETER_ID_REG: u8 = 0x0f;
const MAGNETOMETER_ID_REG: u8 = 0x4f;
#[entry]
fn main() -> ! {
rtt_init_print!();
let board = microbit::Board::take().unwrap();
let mut i2c = { twim::Twim::new(board.TWIM0, board.i2c_internal.into(), FREQUENCY_A::K100) };
let mut acc = [0u8];
let mut mag = [0u8];
// Сначала записываем адрес и регистр на шину, затем читаем ответы микросхем
i2c.write_read(ACCELEROMETER_ADDR, &[ACCELEROMETER_ID_REG], &mut acc)
.unwrap();
i2c.write_read(MAGNETOMETER_ADDR, &[MAGNETOMETER_ID_REG], &mut mag)
.unwrap();
rprintln!("The accelerometer chip's id is: {:#b}", acc[0]);
rprintln!("The magnetometer chip's id is: {:#b}", mag[0]);
loop {
wfi();
}
}
Если не считать инициализации, этот код должен быть понятен, если вы разобрались в протоколе I2C, описанном выше. Инициализация работает так же, как в главе про UART. В конструктор мы передаём периферию, а также выводы, которые используются для связи с чипом, а затем частоту, на которой должна работать шина, — в данном случае 100 кГц (K100, потому что идентификаторы в Rust не могут начинаться с цифры).
Проверка
Как обычно
$ cargo embed --example chip-id
чтобы проверить нашу небольшую программу-пример.