четвер, 26 квітня 2018 р.

STM32: Бібліотека для зручної роботи з PCF857x по I2C

Передмова

Для розширення I/O портів існують різні мікросхеми для різних шин/протоколів. Наприклад мікросхема SN74HC595N з керуванням по SPI, про яку вже була в мене стаття. Або мікросхема PCF8574 з керуваннням по I2C, про яку і буде ця стаття. Існують і інші I/O Expander як 8 бітні, так і 16 бітні. Заглиблюватись в протоколи не будемо, в мережі достатньо якісної літератури по цій темі. Зосередимось на самому розширювачі портів вводу виводу. Писати и читати стан ніжок у PCF8574 будемо по шині I2C за допомоги HAL Driver. І щоб зручно було керувати розширювачем портів PCF8574 створив бібліотеку з необхідним набором функцій. За основу бібліотеки взяв готову бібліотеку PCF8574_ESP для ESP8266 та переніс її на нашу платформу STM32, а також переробив з мови C++ на Ci. В цій статті ознайомимось з роботою I/O Expander PCF8574 в режимі: 4 біти читаємо - 4 біти пишемо. Тобто, під'єднаємо до PCF8574 чотири кнопки і чотири світлодіоди. За допомоги кнопок будемо керувати станом світлодіодів.

Що потрібно

Схема

Зберіть макет за такою схемою та під'єднайте до шини I2C мікроконтролера. Резистори R7 - R10 можна не встановлювати. 
Схема підключення PCF8574 4-входи 4-виходи
Або як вам зручнише, зберіть макет по цьому малюнку
Макет Expander I/O PCF857

CubeMX for STM32

Запускаємо CubeMX, створюємо новий проект, обираємо свій чип STM32. У вкладці "Pinout" робимо такі налаштування шпильок:
  • RCC - Crystal/Ceramic resonator
  • SYS - Serial Wire
  • обираємо порт I2C до якого буде підключено дисплей. Я обрав I2C1
  • обираємо якийсь pin де будемо "ловити" подію по перериваннях від сигналу INT мікросхеми PCF8574. Я обрав PB5, як GPIO_EXTI5 і дав йому мітку PCF8574_INT
Налаштування периферії
Перейдемо до вкладки "Clock Configuration" та налаштуємо тактування мікроконтролеру як зазначено червоним на малюнку:
Наалаштування тактування
У вкладці "Configuration" можна налаштувати порт I2C. На чипові STM32F1xx все працює за замовчуванням на частоті 400кГц. Можна залишити як є. Налаштуємо наш PB5, щоб "ловити" переривання:
  • GPIO mode - external interrput mode with falling edge trigger detection (реагуємо на фронт що спадає)
  • GPIO Pull-Up/Pull-Down - Pull-up (з підтяжкою до живлення, логічної 1)
  • USER Label - PCF8574_INT
Налаштування переривань на PB5
Та в розділі NVIC увімкнемо переривання від PB5:
Увімкнемо переривання
Можна генерувати код програмою CubeMXforSTM32 для совго засобу розробки. В мене це Atolic TrueStudio, професійний засіб розробки абсолютно безкоштовний.

Підключення бібліотеки та демокод

Бібліотека складається з таких файлів:
  • pcf857x.h - початкові налаштування, визначення, прототипи
  • pcf857x.c - функції бібліотеки
Ці файли потрібно вкласти до вашого проекту. Файли з розширенням ".h" до теки "inc", а файли з розшиернням ".c" до теки "src". Або всю теку з бібліотекою, але на деяких засобах розробки потрібно в налаштуваннях проекту прописати шлях до теки з бібліотекою.
Відкриємо в зазобі розробки файл pcf857x.h і налаштуємо бібліотеку для правильної роботи з розширювачем портів:
  • #define STM32F1XX - вказати серію свого чипу
  • #define STM32_I2C_PORT hi2c1 - вказати порт I2C до якого під'єднано PCF8574 
  • #define PCF857x_ADDRESS 0x38 - вказати адресу PCF8574 на шині I2C (відповідно до схеми це буде адреса 0x38
Тепер напишемо демо програмку, щоб оцінити роботу розширювача портів. Відкриваємо файл "main.c" в засобі розробки і в визначених програмою CubeMX місцях для користувача, напишемо такий код:
  • Вкладемо заголовний файл з бібліотекою до нашого коду
/* USER CODE BEGIN Includes */
#include "pcf857x.h"
/* USER CODE END Includes */
  • Напишемо код для демонстрації. Запалимо всі вогники, через пару секунд загасимо всі вогники, зсунемо вогники ліворуч і праворуч та покрутимо вогник праворуч і ліворуч
  /* USER CODE BEGIN 2 */
  if(pcf857x_Init(0xFF, false) != PCF857x_OK)
  {
   while(1);
  }

  pcf857x_ResetInterruptPin();
  
  pcf857x_ToggleAll();
  HAL_Delay(2000);
  pcf857x_ToggleAll();
  HAL_Delay(2000);

  for (int i = 0; i < 4; ++i)
  {
   pcf857x_ShiftLeft(1);
   HAL_Delay(200);
  }

  for (int i = 0; i < 4; ++i)
  {
    pcf857x_ShiftRight(1);
    HAL_Delay(200);
  }

  pcf857x_Write(0, 0);
  HAL_Delay(200);
  for (int i = 0; i < 3; ++i)
  {
  pcf857x_RotateLeft(1);
  HAL_Delay(200);
  }

  for (int i = 0; i < 3; ++i)
  {
  pcf857x_RotateRight(1);
  HAL_Delay(200);
  }

  HAL_Delay(200);
  pcf857x_Write8(0xFF);  
  /* USER CODE END 2 */

Але це не все. В нашій схемі є ще кнопки. І можна відслідковувати стан натискання кожної кнопки і якимсь чином реагувати на це. Можна просто в лоб, в безкінечному циклі читати стан ніжок на expander PCF8574. Але це не розсудливо. Ми постійно читаємо шину I2C, а потім визначаємо було натискання кнопки чи ні, незалежно від того, чи дійсно кнопку натискали, чи нічого взагалі не відбувається. По суті даремно витрачаємо ресурси мікроконтролера і без будь якої користі ганяємо дані по шині I2C.

То що ж робити? На самому початку в програмі CubeMX ми задіяли ще одину шпильку від розширювача з назвою "INT". Шпилька "INT" виставляє логічний нуль, до наступного читання, коли відбулись якісь зміни на шпильках P0 - P7 розширювача портів PCF8574. А "ловити" цей "0" будемо на PB5 мікроконтролеру, який ми налаштували в режим переривань, підтягнули до "1", та "ловитимемо" саме фронт що спадає.

Весь потрібний початковий код з налаштувань периферії зробив за нас CubeMX, то нам залишається тільки написати обробник переривань. Для зручності в драйверах HAL є функція зворотнього виклику "HAL_GPIO_EXTI_Callback". Ця функція викликається кожного переривання і на вхід цій функції дається на якій шпильці відбулась подія.
  • Додамо до коду обробник переривань
/* USER CODE BEGIN PFP */
/* Private function prototypes -----------------------------------------------*/
void HAL_GPIO_EXTI_Callback (uint16_t GPIO_Pin)
{
 if(GPIO_Pin == PCF8574_INT_Pin)
 {
  for (int i = 0; i < 4; ++i)
  {
   if(!pcf857x_Read(i + 4))
   {
    pcf857x_Toggle(i);
   }
  }
 }
}
/* USER CODE END PFP */
  • Безкінечний цикл залишається "пустим", а вся "магія" по перемиканню стану вогників працює за допомоги апаратного переривання, та невеличкого коду в обробнику переривань.
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
  /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */

  }
  /* USER CODE END 3 */
  • Тепер можна компілювати проект, заливати до мікроконтролера та милуватись роботою вогників і реакцією на натискання кнопок

Список функцій бібліотеки


PCF857x_TypeDef pcf857x_Init(uint16_t value_init, bool is8575); // Початкова ініціалізація pcf857x. На вхід число, початковий стан P0 - P7 та false якщо pcf8574 або true якщо pcf8575
uint8_t pcf857x_Read8(void); // Читання 8 бітів з P0 - P7
uint16_t pcf857x_Read16(void); // Читання 16 бітів з P0 - P15
bool pcf857x_Read(uint8_t pin); // Читання шпильки pin на pcf857x

PCF857x_TypeDef pcf857x_Write8(uint8_t value); // Запис 8 бітів до P0 - P7. Повертає код помилки.
PCF857x_TypeDef pcf857x_Write16(uint16_t value); // Запис 16 бітів до P0 - P15. Повертає код помилки.
PCF857x_TypeDef pcf857x_Write(uint8_t pin, bool value); // Запис значення до шпильки pin. Повертає код помилки.

PCF857x_TypeDef pcf857x_Toggle(uint8_t pin); // Поміняти стан шпильки pin. Повертає код помилки.
PCF857x_TypeDef pcf857x_ToggleAll(void); // Поміняти стан всіх шпильок. Повертає код помилки.
PCF857x_TypeDef pcf857x_ShiftRight(uint8_t n); // Зсув праворуч на n бітів. Повертає код помилки.
PCF857x_TypeDef pcf857x_ShiftLeft(uint8_t n); // Зсув ліворуч на n бітів. Повертає код помилки.
PCF857x_TypeDef pcf857x_RotateRight(uint8_t n); // Обертання праворуч на n бітів. Повертає код помилки.
PCF857x_TypeDef pcf857x_RotateLeft(uint8_t n); // Обертання ліворуч на n бітів. Повертає код помилки.
PCF857x_TypeDef pcf857x_ResetInterruptPin(void); // Скидання шпильки INT в початковий стан. Повертає код помилки.
PCF857x_TypeDef pcf857x_GetLastError(void); // Повертає останній код помилки.

Архів з бібліотекою

Бібліотека для зручної роботи з розширювачем портів PCF857X

Немає коментарів:

Дописати коментар