Когда работаешь с I2C устройствами, особенно с китайскими платами без спецификации, нередко возникают проблемы с определением адреса. Причины могут быть разные, например, реверс-инжиниринг какого-нибудь ведомого устройства с МК на борту, в неведомой прошивке которого зашит неведомый нам адрес.
Сканер I2C — это простая программа, которая поможет просканировать весь диапазон адресов I2C и покажет активные адреса. Так можно узнать не только адрес, но и понять работают ли конкретные устройства в принципе, или нет.
Программу будем писать для отладочной платы BluePill с микроконтроллером STM32F103C8T6. Разумеется, она будет работать и для любого другого контроллера из семейства STM32.
Настройка STM32CubeIDE
Наша цель — сканировать I2C шину и о результатах докладывать пользователю при помощи интерфейса UART (по виртуальному COM-порту). Следовательно, активируем оба этих интерфейса на вкладке Connectivity. В разделе USART1 в поле Mode ставим Asynchronous. В разделе I2C1 в поле I2C выбираем I2C.
Никаких настроек у этих интерфейсов менять не нужно. Единственно, запомним скорость UART, обычно по-умолчанию она установлена в 115200 бод.
Настраиваем тактирование от внешнего резонатора, который штатно установлен на BluePill. Для этого на вкладке System Core, в разделе RCC в поле HSE выставляем: Crystal/Ceramic Resonator.
Также зарезервируем PA13 и PA14 за программатором. На той же вкладе, в раздел SYS в поле Debug выставляем Serial Wire. Схема настроенного микроконтроллера будет выглядеть так:
Затем, настраиваем схему тактирования. Тут тоже всё стандартно.
Программа
Итак, идея в том, чтобы пробежаться по всем адресам от 0 до 127 и вызвать для каждого из них стандартную HAL функцию HAL_I2C_IsDeviceReady. Ну и вывести результаты этой проверки в UART, должным образом упорядочив вывод — для порядка.
В разделе пользовательских переменных добавляем всё, что нам может пригодиться:
/* USER CODE BEGIN PV */
uint8_t buf[8] = {0};
uint8_t separator[] = " . ";
uint8_t new_line[] = "\r\n";
uint8_t start_text[] = "Start scanning I2C: \r\n";
uint8_t end_text[] = "\r\nStop scanning";
/* USER CODE END PV */
Далее код самого алгоритма сканирования, тоже в соответствующий пользовательский блок кода:
/* USER CODE BEGIN 2 */
uint8_t row = 0, state;
HAL_Delay(1000);
// процедура сканирования
HAL_UART_Transmit(&huart1, start_text, sizeof(start_text), 128);
for( uint8_t i=1; i<128; i++ ){
state = HAL_I2C_IsDeviceReady(&hi2c1, (uint16_t)(i<<1), 3, 5);
if ( state != HAL_OK ){ // нет ответа от адреса
HAL_UART_Transmit(&huart1, separator, sizeof(separator), 128);
}
else if(state == HAL_OK){ // есть ответ
sprintf(buf, "0x%X", i);
HAL_UART_Transmit(&huart1, buf, sizeof(buf), 128);
}
if( row == 15 ){
row = 0;
HAL_UART_Transmit(&huart1, new_line, sizeof(new_line), 128);
} else
row ++;
}
HAL_UART_Transmit(&huart1, end_text, sizeof(end_text), 128);
/* USER CODE END 2 */
Напоминаю, если что-то записать вне выделенного для пользователя места, то CubeIDE всё это почикает при компиляции программы.
Загружаем программу на stm32 через St-Link. Подключаем какое-нибудь I2C устройство, например, OLED дисплей 0.96. Затем через USB-UART соединяем всё это к ПК и открываем любой удобный терминал для COM порта. Например, подойдёт встроенный терминал в Arduino IDE.
Ага, попался! Вот он этот дисплей — 0x3D. Теперь можно смело использовать полученный адрес для работы с этим устройством.