STM32, послідовний інтерфейс I2С. STM32, послідовний інтерфейс I2С Stm32 інтерфейс i2c опис продовження

Хтось любить пиріжки, а хтось – ні.

Інтерфейс i2c широко поширений та використовується. У stm32f4 модулів, що реалізують цей протокол, аж три штуки.
Звичайно, з повною підтримкоювсієї цієї справи.

Робота з модулем, загалом, така сама, як і в інших контролерах: даєш йому команди, він їх виконує і звітує про результат:
Я> Ішли START.
S> Ок, послав.
Я> Круто, йшли адресу тепер. Ось такий: 0xXX.
S> Ок, послав. Мені сказали, що ACK. Давай далі.
Я> Живий ще, добре. Ось тобі номер регістра: 0xYY - йшли.
S> Надіслав, отримав ACK.
Я> Ішли йому тепер дані, ось тобі байт: 0xZZ.
S> Послав він згоден на більше: ACK.
Я> Фіг йому, а не ще. Йшли STOP.
S> Okay.

І все приблизно в такому дусі.

У даному контролерівисновки i2c розкидані по портах таким чином:
PB6: I2C1_SCL
PB7: I2C1_SDA

PB8: I2C1_SCL
PB9: I2C1_SDA

PB10: I2C2_SCL
PB11: I2C2_SDA

PA8: I2C3_SCL
PC9: I2C3_SDA
Взагалі, розпинування периферії зручно дивитися на 59 сторінці.

Що дивно, але для роботи з i2c потрібні всі його регістри, благо їх небагато:
I2C_CR1- команди модулю для відправки команд/станів та вибір режимів роботи;
I2C_CR2- налаштування DMA та вказівка ​​робочої частоти модуля (2-42 МГц);
I2C_OAR1- Налаштування адреси пристрою (для slave), розмір адреси (7 або 10 біт);
I2C_OAR2- Налаштування адреси пристрою (якщо адреси дві);
I2C_DR- Регістр даних;
I2C_SR1- Регістр стану модуля;
I2C_SR2- регістр статусу (slave, повинен читатись, якщо встановлено прапори ADDR або STOPF в SR1);
I2C_CCR- Налаштування швидкості інтерфейсу;
I2C_TRISE- Налаштування таймінгів фронтів.

Втім, половина з них на кшталт «записати та забути».

На платі STM32F4-Discovery вже є I2C пристрій, з яким можна попрактикуватися: CS43L22, аудіоЦАП. Він підключений до висновків PB6/PB9. Головне, не забути подати високий рівень на виведення PD4 (там сидить ~RESET), інакше ЦАП не відповідатиме.

Порядок налаштування приблизно такий:
1 . Дозволити тактування портів та самого модуля.
Нам необхідні висновки PB6/PB9, тому треба встановити біт 1 (GPIOBEN) у регістрі RCC_AHB1ENR, щоб порт завівся.
І встановити біт 21 (I2C1EN) у регістрі RCC_APB1ENR, щоб увімкнути модуль I2C. Для другого та третього модуля номери бітів 22 та 23 відповідно.
2 . Далі налаштовуються висновки: вихід Oped Drain (GPIO->OTYPER), режим альтернативної функції (GPIO->MODER), номер альтренативної функції (GPIO->AFR).
За бажанням можна налаштувати підтяжку (GPIO->PUPDR), якщо її немає на платі (а підтяжка до живлення обох ліній потрібна у будь-якому вигляді). Номер для I2C завжди той самий: 4. Приємно, що для кожного типу периферії заведено окремий номер.
3 . Вказується поточна частота тактування периферії Fpclk1 (виражена МГц) в регістрі CR2. Я так зрозумів, що це потрібно для розрахунку різних таймінгів протоколу.
До речі, вона має бути не менше двох для звичайного режиму та не менше чотирьох для швидкого. А якщо потрібна повна швидкість 400 кГц, то вона ще й повинна ділитися на 10 (10, 20, 30, 40 МГц).
Максимально дозволена частота тактування: 42 МГц.
4 . Настроюється швидкість інтерфейсу в регістрі CCR, вибирається режим (звичайний/швидкий).
Cмисл такий: Tsck = CCR * 2 * Tpckl1, тобто. період SCK пропорційний CCR (для швидкого режиму все трохи хитріший, але в RM розписано).
5 . Налаштовується максимальний час наростання фронту у регістрі TRISE. Для стандартного режиму цей час становить 1 мкс. У регістр треба записати кількість тактів шини, що укладаються тим часом, плюс один:
якщо такт Tpclk1 триває 125 нс, то записуємо (1000 нс/125 нс) + 1 = 8 + 1 = 9.
6 . За бажанням дозволяється генерація сигналів переривання (помилки, стан та даних);
7 . Модуль включається: прапор PE у регістрі CR1 переводиться в 1.

Далі модуль працює вже як слід. Потрібно лише реалізувати правильний порядок команд та перевірки результатів. Наприклад, запис регістру:
1 . Спочатку потрібно відправити START, встановивши прапор із таким ім'ям у регістрі CR1. Якщо все ок, то через деякий час виставиться прапор SB у регістрі SR1.
Хочу помітити один момент, якщо немає підтяжки на лінії (і вони в 0), то цей прапор можна не дочекатися зовсім.
2 . Якщо прапор таки дочекалися, то надсилаємо адресу. Для семибітної адреси просто записуємо його в DR прямий у такому вигляді, як він буде на лінії (7 біт адреси + біт напряму). Для десятибитного складніший алгоритм.
Якщо пристрій відповість на адресу ACK"ом, то в регістрі SR1 з'явиться прапор ADDR. Якщо ні, прапор AF (Acknowledge failure).
Якщо з'явився ADDR, треба прочитати регістр SR2. Можна нічого там і не дивитися, просто послідовне читання SR1 та SR2 скидає цей прапор. А поки прапор встановлений, SCL утримується майстром у низькому стані, що корисно, якщо треба попросити дистанційний пристрій почекати з надсиланням даних.
Якщо все ок, далі модуль перейде в режим прийому або передачі даних в залежності від молодшого біта відправленої адреси. Для запису він має бути нулем, для читання – одиницею.
але ми розглядаємо запис, тому приймемо, що там був нуль.
3 . Далі надсилаємо адресу регістру, який нас цікавить. Так само, записавши його в DR. Після передачі виставиться прапор TXE (буфер передачі порожній) та BTF (передача завершена).
4 . Далі йдуть дані, які можна надсилати, доки пристрій відповідає ACK. Якщо відповіддю буде NACK, ці прапори не встановляться.
5 . Після завершення передачі (або у разі непередбаченого стану) відправляємо STOP: встановлюється однойменний прапор у регістрі CR1.

При читанні все те саме. Змінюється лише після запису адреси регістра.
Замість запису даних йде повторне відправлення START (повторний старт) та відправлення адреси із встановленим молодшим бітом (ознака читання).
Модуль буде чекати на дані від пристрою. Щоб заохочувати його до відправки наступних байт, перед прийомом встановити прапор ACK в CR1 (щоб після прийому модуль посилав цей самий ACK).
Як набридне, прапор знімаємо, пристрій побачить NACK і замовкне. Після чого шлемо STOP звичайним порядком і радіємо прийнятим даним.

Ось те саме у вигляді коду:
// Ініціалізація модуля void i2c_Init (void) (uint32_t Clock = 16000000UL; // Частота тактування модуля (system_stm32f4xx.c не використовується) uint32_t Speed ​​= 100000UL; // 100 кГцB HB1ENR_GPIOBEN; // Налаштуємо висновки PB6, PB9 // Open drain!GPIOB->OTYPER |= GPIO_OTYPER_OT_6|GPIO_OTYPER_OT_9;// Підтяжка зовнішня, тому тут не налаштовується!// якщо треба, див. регістр GPIOB->PUPDR // Номер альтернативної функції GPIOB ->AFR &= ~(0x0FUL<< (6 * 4)); // 6 очистим GPIOB->AFR |= (0x04UL<< (6 * 4)); // В 6 запишем 4 GPIOB->AFR &= ~(0x0FUL<< ((9 - 8) * 4)); // 9 очистим GPIOB->AFR |= (0x04UL<< ((9 - 8) * 4)); // В 9 запишем 4 // Режим: альтернативная функция GPIOB->MODER &= ~((0x03UL<< (6 * 2)) | (0x03UL << (9 * 2))); // 6, 9 очистим GPIOB->MODER |= ((0x02UL<< (6 * 2)) | (0x02UL << (9 * 2))); // В 6, 9 запишем 2 // Включить тактирование модуля I2C1 RCC->APB1ENR | = RCC_APB1ENR_I2C1EN; // На даний момент I2C має бути вимкнений // Скинемо все (SWRST == 1, скидання) I2C1->CR1 = I2C_CR1_SWRST; // PE == 0, це головне I2C1->CR1 = 0; // Вважаємо, що запущені від RC (16 МГц) // Преддільників у системі тактування немає (всі 1) // По-хорошому, треба було б обчислювати це все з // реальної частоти тактування модуля I2C1->CR2 = Clock / 1000000UL; // 16 МГц // Налаштовуємо частоту ( // Tclk = (1 / Fperiph); // Thigh = Tclk * CCR; // Tlow = Thigh; // Fi2c = 1 / CCR * 2; // CCR = Fperiph / ( Fi2c * 2);uint16_t Value = (uint16_t)(Clock / (Speed ​​* 2)); // Мінімальне значення: 4 if(Value< 4) Value = 4; I2C1->CCR = Value; ) // Задаємо граничний час фронту // У стандартному режимі цей час 1000 нс // Просто додаємо до частоти, що у МГц одиницю (див. RM стор. 604). I2C1->TRISE = (Clock / 1000000UL) + 1; // Включимо модуль I2C1->CR1 | = (I2C_CR1_PE); // Тепер можна щось робити) // Надіслати байт bool i2c_SendByte(uint8_t Address, uint8_t Register, uint8_t Data) ( if(!i2c_SendStart()) return false; // Адреса мікросхеми if(!i2c_SendAddress(Address)) (); // Адреса регістру if(!i2c_SendData(Register)) return i2c_SendStop(); // Дані if(!i2c_SendData(Data)) return i2c_SendStop(); // Стоп! i2c_SendStop(); return true; Отримати байт bool i2c_ReceiveByte(uint8_t Address, uint8_t Register, uint8_t * Data) ( if(!i2c_SendStart()) return false; // Адреса мікросхеми if(!i2c_SendAddress(Address)) Return i2 i2c_SendData(Register)) return i2c_SendStop(); // Повторний старт if(!i2c_SendStart()) return false; // Адреса мікросхеми (читання) if(!i2c_SendAddress(Address | 1)) // i2c_SendStop(); if(!i2c_ReceiveData(Data)) return i2c_SendStop(); // Стоп! i2c_SendStop(); return true; (це треба зробити як-небудь) // Відправлення байта в пристрій з адресою 0x94, регістр 0x00 зі значенням 0x00. i2c_SendByte(0x94, 0x00, 0x00); // Прийом байта з пристрою з адресою 0x94 з регістра 0x01 (ID) змінну buffer i2c_ReceiveByte(0x94, 0x01, &ID); )
Звичайно, окрім як у навчальному прикладі, так робити не можна. Очікування закінчення дії занадто довге для такого швидкого контролера.

(Керівництво розробника з мікроконтролерів сімейства HCS08)

Для управління модулем I2C використовуються 6 регістрів спеціальних функцій:

  • IICC - перший регістр управління модуля I2C;
  • IICC2 - другий регістр управління модуля I2C;
  • IICS - регістр стану модуля I2C;
  • IICF - регістр швидкості обміну модуля I2C;
  • IICA - регістр адреси модуля I2C;
  • IICD - регістр даних модуля I2C.

У МК серії QE присутні 2 модулі I2C і, відповідно, по два регістри управління кожного типу. Наприклад, перший регістр стану IIC1S та другий регістр стану IIC2S.

11.2.8.1. Реєстр управління IICC

Для МК серії AC. AW, Dx, EL, GB, GT, JM, LC, QE. QG. SG, SH, SL
Реєстр Режим D7 D6 D5 D4 D3 D2 D1 D0
IICC Читання IICEN IICIE MST TX TXAK 0 0 0
Запис RSTA
Скидання 0 0 0 0 0 0 0 0
Опис бітів:
Ім'я бита Опис Символ у мові С
IICEN Біт дозволу роботи модуля I2C:
0 - контролер I2C вимкнений;
1 - контролер I2C включений.
bIICEN
IICIE Біт дозволу переривання модуля від I2C:
0 - переривання за запитом I2C заборонені;
1 - переривання на запит I2C дозволені.
bHCIE
MST Біт вибору режиму роботи контролера I2C:
0 - контролер I2C працює в режимі веденого (Slave);
1 - контролер I2C працює в режимі ведучого (Master).
Коли цей біт змінюється з 0 на 1, стан Старт генерується. Навпаки, коли біт змінюється із 1 на 0, генерується стан Стоп.
bMST
TX Біт вибору напряму передачі даних даних SDA:
0 - лінія працює на введення;
1 - лінія працює на висновок.
bTX
TXAK Біт підтвердження в режимі прийому:
0 - генерується біт підтвердження після прийому байта;
1 - генерується біт непідтвердження прийому байта.
Цей біт управляє генерацією біта підтвердження після прийому байта даних, незалежно від того, чи це ведомий чи ведучий.
bTXAK
RSTA Якщо модуль I2C працює в режимі ведучого, запис 1 в цей біт викликає повторну генерацію стану Старт - «Повторний старт». bRSTA

11.2.8.2. Реєстр стану IICS

Для МК серій AC, AW, Dx, EL, GB, GT, JM, LC, QE, QG, SG, SH, SL
Реєстр Режим D7 D6 D5 D4 D3 D2 D1 D0
IICS Читання TCF IAAS BUSY ARBL 0 SRW IICIF RXAK
Запис
Скидання 0 0 0 0 0 0 0 0
Опис бітів:
Ім'я бита Опис Символ у мові С
TCF Біт завершення обміну. Встановлюється після завершення обміну одним байтом:
0 - обмін не завершений;
1 — обмін завершено.
Прапор TCF скидається в 0, коли читають регістр даних IICD (у режимі прийому) або коли записують регістр даних IICD (у режимі передачі).
bTCF
IAAS Прапор адреси веденого. Встановлюється, якщо пристрій працює в режимі веденого та передана в повідомленні ведучого адресу дорівнює адресі веденого, який зберігається в регістрі адреси IICA.
Прапор скидається під час запису в регістр IICC.
bIAAS
BUSY Прапор зайнятої лінії. Цей прапор встановлюється, якщо модуль I2C розпізнав старт-біт лінії. Прапор скидається, коли модуль розпізнає стоп-біт лінії.
0 - шина I2C вільна;
1 - шина I2C зайнята.
bBUSY
ARBL Прапор втрати арбітражу:
0 - немає порушень у роботі шини I2C;
1 - є втрата арбітражу. Модуль I2C повинен деякий час зачекати, а потім розпочати операцію передачі знову.
bARBL
SRW Біт напряму передачі веденого. Цей біт показує стан біта R/W у полі адреси:
0 - ведений приймає. Ведучий передає веденому;
1 - ведений передає. Ведучий приймає від веденого.
bSRW
IICIF Прапор необслуговуваних запитів на переривання модуля I2C. Встановлюється в 1, якщо встановлено один із прапорів: TCF, IAAS або ARBL.
0 - немає необслужених переривань;
1 - є необслужені переривання.
Прапор скидається за допомогою запису до нього 1.
bIICIF
RXAK Біт підтвердження прийому ведучого:
0 - ведений підтвердив прийом даних;
1 - ведений не підтвердив прийом даних.
Цей біт відбиває стан поля ASK на часовій діаграмі обміну.
bRXAK

11.2.8.3. Реєстр адреси IICA

Реєстр Режим D7 D6 D5 D4 D3 D2 D1 D0
IICA Читання ADDR
Запис
Скидання 0 0 0 0 0 0 0 0

У цьому регістрі зберігається 7-бітна адреса веденого пристрою, який розробник надав даному пристроюрозробки системи. Ця адреса автоматично порівнюється з кодом адреси, який ведений отримав у полі адреси по шині I2C. Якщо адреси збіглися, то встановлюється біт IAAS у регістрі стану IICS.

11.2.8.4. Реєстр швидкості обміну IICF

Для МК серій AC, AW, Dx, EL, GB, GT, JM, LC, QE, QG, SG, SH, SL
Реєстр Режим D7 D6 D5 D4 D3 D2 D1 D0
IICF Читання MULT ICR
Запис
Скидання 0 0 0 0 0 0 0 0
Опис бітів:

Значення коефіцієнтів SCL_DIV та SDA_HV

ISR SCL_DIV SDA_HV ISR SCL_DIV SDA_HV
0x00 20 7 0x20 160 17
0x01 22 7 0x21 192 17
0x02 24 8 0x22 224 33
0x03 26 8 0x23 256 33
0x04 28 9 0x24 288 49
0x05 30 9 0x25 320 49
0x06 34 10 0x26 384 65
0x07 40 10 0x27 480 65
0x08 28 7 0x28 320 33
0x09 32 7 0x29 384 33
0x0A 36 9 0x2A 448 65
0x0B 40 9 0x2B 512 65
0x0C 44 11 0x2C 576 97
0x0D 48 11 0x2D 640 97
0x0E 56 13 0x2E 768 129
0x0F 68 13 0x2F 960 129
0x10 48 9 0x30 640 65
0x11 56 9 0x31 768 65
0x12 64 13 0x32 896 129
0x13 72 13 0x33 1024 129
0x14 80 17 0x34 1152 193
0x15 88 17 0x35 1280 193
0x16 104 21 0x36 1536 257
0x17 128 21 0x37 1920 257
0x18 80 9 0x38 1280 129
0x19 96 9 0x39 1536 129
0x1A 112 17 0x3A 1792 257
0x1B 128 17 0x3B 2048 257
0x1C 144 25 0x3C 2304 385
0x1D 160 25 0x3D 2560 385
0x1E 192 33 0x3E 3072 513
0x1F 240 33 0x3F 3840 513

У цьому реєстрі зберігаються два бітових поля, які визначають швидкість і параметри тимчасової діаграми обміну по I2C. Частота сигналів синхронізації визначається за такою формулою:

Час встановлення даних SDA_hold_time на шині I2C - це часовий інтервал між моментом встановлення 0 сигналу SCL і зміною даних на лінії SDA. Призначається параметром SDA_HV (SDA_Hold_Value) з таблиці фактора ICR регістра швидкості обміну:

.

11.2.8.5. Регістр даних IICD

Для МК серій AC, AW, Dx, EL, GB, GT, JM, LC, QE, QG, SG, SH, SL
Реєстр Режим D7 D6 D5 D4 D3 D2 D1 D0
IICD Читання I2C DATA
Запис
Скидання 0 0 0 0 0 0 0 0

Якщо модуль I2C працює в режимі ведучого, то операція запису в цей регістр ініціює обмін по I2C (але якщо біт напряму обміну в регістрі управління IICC виставлений правильно, тобто TX = 1). Перший байт після стану Старт, який програма записує в регістр даних, інтерпретується відомими як адресу пристрою. Тому програма має правильно сформувати вміст першого байта. Операція читання регістру повертає останній прийнятий байт I2C. Операція читання регістру також ініціює початок прийому наступного байта, але якщо біт напрями обміну в регістрі управління IICC виставлений правильно, тобто. за TX = 0! При TX = 1 операція читання регістру не викликає прийом нового байта по I2C від веденого.

Якщо модуль I2C працює в режимі веденого, записані в цей регістр дані будуть передані на лінію SDA шини I2C тоді, коли провідний пристрій буде виконувати цикл прийому від цього веденого. Операція читання регістру повертає останній байт від ведучого.

11.2.8.6. Реєстр управління IICC2

Для МК серій AC, Dx, EL, JM, QE, SG, SH, SL
Реєстр Режим D7 D6 D5 D4 D3 D2 D1 D0
IICC Читання GCAEN ADEXT 0 0 0 AD10 AD9 AD8
Запис
Скидання 0 0 0 0 0 0 0 0

Перші кроки з STM32 та компілятором mikroC для ARM архітектури - Частина 4 - I2C, pcf8574 та підключення LCD на базі HD4478

Наступну статтю я хочу присвятити роботі з поширеним інтерфейсом i2c, який досить часто використовується в різноманітних мікросхемах, що підключаються до мікроконтролера.

I2C є шиною, що працює за двома фізичними сполуками (крім загального дроту). Досить багато про неї розписано в Інтернеті, непогані статті є у Вікіпедії. Крім того, алгоритм роботи шини дуже зрозуміло описаний. Коротко, шина представлять собою двопровідну синхронну шину. На шині може одночасно перебувати до 127 пристроїв (адреса пристрою 7-бітна, до цього повернемося далі). Нижче наведена типова схема підключення пристроїв до шини i2c, з МК як провідний пристрій.


Для i2c всі пристрої (як майстер, так і слейви) використовують open-drain виходи. Простіше кажучи, вони можуть притягувати шину ТІЛЬКИ ДО ЗЕМЛІ. Високий рівень шаші забезпечується підтягуючими резисторами. Номінал цих резисторів зазвичай вибирається в діапазоні від 4,7 до 10 кОм. i2c досить чутлива до фізичних ліній, що з'єднують пристрої, тому якщо використовується з'єднання з великою ємністю (наприклад довгий тонкий або екранований кабель), вплив цієї ємності може «розмити» фронти сигналів і перешкодити нормальній роботішини. Чим менше підтягує резистор, тим менше впливає ця ємність на характеристику фронтів сигналу, але ТИМ БІЛЬШЕ НАВАНТАЖЕННЯ на вихідні транзистори на інтерфейсах i2c. Значення цих резисторів підбирається для кожної конкретної реалізації, але вони не повинні бути менше 2,2 комів, інакше можна просто спалити вихідні транзистори в пристроях, що працюють з шиною.

Шина складається з двох ліній: SDA (лінії даних) та SCL (тактуючий сигнал). Тактує шину Майстер пристрійзазвичай наш МК. Коли SCL високий рівень інформація зчитується з шини даних. Змінювати стан SDA можна лише за низького рівня тактуючого сигналу. При високому рівні SCL сигнал на SDA змінюється під час формування сигналів START (при високому рівні SCL сигнал на SDA змінюється високого на низький) та STOP - при високому рівні SCL сигнал SDA змінюється з низького на високий).

Окремо слід сказати, що i2c адреса задається 7-бітним числом. 8 - молодший біт вказує напрямок передачі даних 0 - означає, що слейв буде передавати дані, 1 - приймати.. Коротко алгоритм роботи з i2c такий:

  • Високий рівень на SDA і SCL- шина вільна, можна розпочинати роботу
  • Майстер піднімає SCLв 1, і змінює стан SDA c 1 на 0 – притягує його до землі – формується сигнал START
  • Майстер передає 7-бітну адресу слейва з бітом напрямку (дані на SDAвиставляються коли SCLпритягнуті до землі, і читаються слейвом (коли він відпущений). Якщо слейв не встигає "сховати" попередній біт, він притягує SCLдо землі, даючи зрозуміти майстру, що стан шини даних не потрібно змінювати: «ще читаю попередній». Після того, як майстер відпустив шину він перевіряє, чи відпустив її слейв.
  • Після передачі 8 біт адреси майстер генерує 9 такт і відпускає шину даних. Якщо слейв почув і свою адресу і прийняв його, то він притисне SDAдо землі. Так формується сигнал ASK- Прийняв, все ОК. Якщо слейв нічого не зрозумів, або його просто там немає, то нікому буде притиснути шину. майстер почекає тайм-аут і зрозуміє, що його не зрозуміли.
  • Після передачі адреси, якщо у нас виставлений напрямок від майстра до слейву(8 біт адреси дорівнює 1), то майстер передає дані в слейв, не забуваючи після передачі кожного байта перевіряти наявність ASKвід слейву, чекаючи обробки інформації, що надійшла веденим пристроєм.
  • При прийомі майстром даних від слейву майстер сам формує сигнал ASKпісля прийому кожного байта, а слейв контролює його наявність. Майстер може спеціально не надіслати ASKперед відправкою команди STOP, Як правило, так даючи зрозуміти відомому, що більше зраджувати дані не потрібно.
  • Якщо після надсилання даних майстром (режим запису) необхідно прочитати дані зі слейва, то майстер формує знову сигнал START надсилаючи адресу слейва з прапором читання. (якщо перед командою STARTне було передано STOPто формується команда RESTART). Це використовується для зміни напряму спілкування майстра-слейв. Наприклад, ми передаємо слейву адресу регістру, а потім читаємо з нього дані.)
  • Після закінчення роботи зі слейвом майстер формує сигнал STOP- при високому рівні тактує сигналу формує перехід шини даних з 0 в 1.
У STM 32 є апаратно реалізовані приймачі шини i2c. Таких модулів МК може бути 2 або 3. Для їх конфігурації використовуються спеціальні регістри, описані в референсі до використовуваного МК.

У MicroC перед використанням i2c (як і будь-якої периферії) її необхідно належним чином проініціалізувати. Для цього використовуємо таку функцію (Ініціалізація як майстер):

I2Cn_Init_Advanced(unsigned long: I2C_ClockSpeed, const Module_Struct *module);

  • n- номер використовуваного модуля, наприклад I2C1або I2C2.
  • I2C_ClockSpeed- Швидкість роботи шини, 100000 (100 kbs, стандартний режим) або 400000 (400 kbs, швидкий режим). Другий у 4 рази швидше, але його підтримують не всі пристрої
  • *module- покажчик на периферійний модуль, наприклад &_GPIO_MODULE_I2C1_PB67, тут не забуваємо що Code Assistant (ctrl-пробіл ) дуже допомагає.
Для початку перевіримо вільність шини, для цього існує функція I2Cn_Is_Idle(); 1, що повертає шина вільна, і 0 якщо по ній йде обмін.

I2Cn_Start();
де n- Номер використовуваного модуля i2c нашого мікроконтролера. Функція поверне 0, якщо на шині виникла помилка і 1 якщо все ОК.

Для того щоб передати дані слейву використовуємо функцію:

I2Cn_Write(unsigned char slave_address, unsigned char *buf, unsigned long count, unsigned long END_mode);

  • n- номер використовуваного модуля
  • slave_address- 7-бітна адреса слейву.
  • *buf- Вказівник на наші дані - байт або масив байтів.
  • count- кількість переданих байт даних.
  • END_mode- що робити після передачі даних слейву, END_MODE_STOP - передати сигнал STOP, або END_MODE_RESTART знову відправити START, сформувавши сигнал RESTARTі давши зрозуміти відомству, що сеанс роботи з ним не закінчено і з нього зараз читатимуть дані.
Для читання даних зі слейву використовується функція:

I2Cn_Read (char slave_address, char *ptrdata, unsigned long count, unsigned long END_mode);

  • n- номер використовуваного модуля
  • slave_address- 7-бітна адреса слейву.
  • *buf- покажчик на змінну або масив, в який ми приймаємо дані, тип char або short int
  • count- кількість байт даних, що приймаються.
  • END_mode- що робити після прийому даних від слейву - END_MODE_STOP - передати сигнал STOP, або END_MODE_RESTART надіслати сигнал RESTART.
Спробуємо щось підключити до нашого МК. Для початку: поширену мікросхему PCF8574(A) являє собою розширювач портів введення виводу з керуванням по шині i2c. Ця мікросхема містить лише один внутрішній регістр, що є її фізичним портом введення-виведення. Тобто якщо їй передати байт, він відразу виставиться на її висновки. Якщо рахувати з неї байт (Передати STARTадреса з прапор читання, сигнал RESTERT,прочитати дані та наприкінці сформувати сигнал STOP) то він відобразить логічні стани на її висновках. Підключимо нашу мікросхему відповідно до датаситу:


Адреса мікросхеми формується із стану висновків A0, А1, А2. Для мікросхеми PCF8574адреса буде: 0100A0A1A2. (Наприклад, у нас A0, А1, А2 мають високий рівень, відповідно адреса нашої мікросхеми буде 0b0100 111 = 0x27). Для PCF8574A - 0111A0A1A2, що з нашою схемою підключення дасть адресу 0b0111 111 = 0x3F. Якщо, допустимо A2 з'єднати із землею, то адреса для PCF8574Aбуде 0x3B. Разом на одну шину i2c можна одночасно повісити 16 мікросхем, 8 PCF8574A і PCF8574.

Спробуємо щось передати ініціалізувати i2c шину і щось передати нашій PCF8574.

#define PCF8574A_ADDR 0x3F //Адреса нашої PCF8574 void I2C_PCF8574_WriteReg(unsigned char wData) ( I2C1_Start(); // Формуємо сигнал START I2C1_Write(PCF8574A_ADDR,&W; Data; формуємо сигнал STOP) char PCF8574A_reg ; // змінна яку ми пишемо в PCF8574 void main () (I2C1_Init_Advanced(400000, &_GPIO_MODULE_I2C1_PB67); // Запускаємо I2C delay_ms(25); // Трохи зачекаємо PCF8574A_reg.b0 1 = 1; // погасимо другий світлодіод while (1) ( delay_ms(500); PCF8574A_reg.b0 = ~PCF8574A_reg.b0; PCF8574A_reg.b1 = ~PCF8574A_reg.b1; //інвертуємо стан світлодіодів I2C_PCF85 PCF8574 дані ) )
Компілюємо та запускаємо нашу програму і бачимо, що наші світлодіоди поперемінно моргають.
Я не просто так підключив катодом світлодіоди до нашої PCF8574. Вся справа в тому, що мікросхема при подачі на вихід логічного 0 чесно притягує свій висновок до землі, а ось при подачі логічної 1 підключає його до живлення через джерело струму в 100 мкА. Тобто «чесної» логічної 1 на виході не отримати. І світлодіод від 100 мкА не запалити. Зроблено це у тому, щоб без додаткових регістрів налаштовувати виведення PCF8574 на вхід. Ми просто пишемо у вихідний регістр 1 (фактично встановлюємо стан ніжки в Vdd) і можемо просто коротити його на землю. Джерело струму не дасть згоріти вихідному каскаду нашого розширювача вводу/виводу. Якщо ніжка притягнута до землі, то на ній потенціал землі і читається логічний 0. Якщо ніжка притягнута до +, то читається логічна 1. З одного боку просто, але з іншого, про це завжди потрібно пам'ятати, працюючи з даними мікросхемами.


Спробуймо прочитати стан висновків нашої мікросхеми-розширювача.

#define PCF8574A_ADDR 0x3F //Адреса нашої PCF8574 void I2C_PCF8574_WriteReg(unsigned char wData) ( I2C1_Start(); // Формуємо сигнал START I2C1_Write(PCF8574A_ADDR, &DData, &Data; формуємо сигнал STOP) void I2C_PCF8574_ReadReg (unsigned char rData) (I2C1_Start(); // Формуємо сигнал START I2C1_Read(PCF8574A_ADDR, &rData, 1, END_MODE_STOP); // Читаємо 1 байт даних і формуємо сигнал STOP) char PCF8574A_reg; //Змінна яку ми пишемо в PCF8574 char PCF8574A_out; // Змінна в яку ми читаємо і PCF8574 char lad_state; //включений чи вимкнений наш світлодіод void main () ( I2C1_Init_Advanced(400000, &_GPIO_MODULE_I2C1_PB67); // Запускаємо I2C delay_ms(25); // Трохи зачекаємо PCF8574A_reg.b0 = 0; 0; ;/ / погасимо другий світлодіод PCF8574A_reg.b6 = 1; // Притягнемо висновки 6 і 7 до живлення.PCF8574A_reg.b7 = 1; 74_ReadReg (PCF8574A_out ) // читаємо з РCF8574 if (~PCF8574A_out.b6) PCF8574A_reg.b0 = ~PCF8574A_reg.b0; // Якщо натиснута 1 кнопка (6 біт прочитаного байта з РCF8574 дорівнює 0, то ввімкнемо/ .b7) PCF8574A_reg.b1 = ~PCF8574A_reg.b1;// аналогічно для 2 кнопки і 2 світлодіода ) )
Тепер натискаючи на кнопочки ми вмикаємо чи відключаємо наш світлодіод. У мікросхеми є ще висновок INT. На ньому формується імпульс щоразу, коли змінюється стан висновків нашого розширювача вводу/виводу. Підключивши його у вході зовнішнього переривання нашого МК (як настроїти зовнішні переривання і як з ними працювати я розповім в одній із наступних статей).

Давайте використовуючи наш розширювач портів, підключимо через нього символьний дисплей. Таких існує безліч, але практично всі вони побудовані на базі чіпа-контролера HD44780та його клонів. Наприклад, я використовував дисплей LCD2004.


Даташити на нього і контролер HD44780 можна легко знайти в Інтернеті. Підключимо наш дисплей до РCF8574, а її відповідно до нашого STM32.

HD44780використовує паралельний інтерфейс, що стробується. Дані передаються по 8 (за один такт) або 4 (за 2 такти) стробуючого імпульсу на висновку E. (Читаються контролером дисплея по низхідному фронту, переходу з 1 в 0). Висновок RSвказує чи передаємо ми нашому дисплею дані ( RS = 1) (символи які він повинен відобразити, фактично з ASCII коди) або команди ( RS = 0). RWпоказує напрям передачі, запис чи читання. Зазвичай ми пишемо дані в дисплей, тому ( RW = 0). Резистор R6 керує контрастністю дисплея. Просто підключати вхід регулювання контрастності до землі або живлення не можна, інакше нічого не побачите.. VT1 служить для включення та вимикання підсвічування дисплея за командами МК. MicroC є бібліотека для роботи з такими дисплеями по паралельному інтерфейсу, але зазвичай, витрачати на дисплей 8 ніг накладно, тому я практично завжди використовую РCF8574 для роботи з такими екранчиками. (Якщо комусь буде цікаво, то напишу статтю про роботу з дисплеями на базі HD44780 вбудованими в MicroC за паралельним інтерфейсом.) Протокол обміну не надто складний (ми будемо використовувати 4 лінії даних і передавати інформацію за 2 такти), його наочно показує наступна тимчасова діаграма:


Перед передачею даних на дисплей його треба проініціалізувати, передавши службові команди. (описані в датасіті, тут наведемо тільки ті, що використовуються)

  • 0x28- зв'язок із індикатором по 4 лініях
  • 0x0C- Включаємо виведення зображення, відключаємо відображення курсору
  • 0x0E- Включаємо виведення зображення, включаємо відображення курсору
  • 0x01- Очищаємо індикатор
  • 0x08- відключаємо виведення зображення
  • 0x06- після виведення символу курсор зсувається на 1 знайоме місце
Так як нам буде потрібно досить часто працювати з цим індикатором то створимо бібліотеку, що підключається. "i2c_lcd.h" . Для цього в Project Maneger Header Files і виберемо Add New File . Створимо наш заголовний файл.

#define PCF8574A_ADDR 0x3F //Адреса нашої PCF8574 #define DB4 b4 // Відповідність висновків PCF8574 та індикатора #define DB5 b5 #define DB6 b6 #define DB7 b7 #define EN b3 #define R керування підсвічуванням #define displenth 20 // кількість символів у рядку нашого дисплея static unsigned char BL_status; // Змінна зберігає стан підсвічування (вкл / викл) void lcd_I2C_Init (void); // Функція ініціалізації дисплея та PCF8574 void lcd_I2C_txt(char *pnt); // Виводить на екран рядок тексту, параметр - покажчик цього рядка void lcd_I2C_int(int pnt); // Виводить на екран значення цілісної змінної, параметр - значення void lcd_I2C_Goto (unsigned short row, unsigned short col); // переміщає курсор на зазначену позицію, параметри row - рядок (від 1 до 2 або 4 в залежності від дисплея) та col - (від 1 до displenth)) void lcd_I2C_cls(); // Очищає екран void lcd_I2C_backlight (unsigned short int state); // Вмикає (при передачі 1 і відключає - під час передачі 0 підсвічування дисплея)
Тепер опишемо наші функції, знову йдемо в Project Manegerклацніть правою кнопкою по папці Sources і виберемо Add New File . Створюємо файл "i2c_lcd.с" .

#include "i2c_lcd.h" //інклудим наш хедер-файл char lcd_reg; //реєстр тимчасового зберігання даних, що відправляються в PCF8574 void I2C_PCF8574_WriteReg(unsigned char wData) //функція відпарвування даних по i2c в чіп PCF8574 (I2C1_Start(); I2C1_Write(PCF8574A_ADDR,& (char com) / /функція відправки команди нашому дисплею (lcd_reg = 0; // пишемо 0 в тимчасовий регістр lcd_reg.BL = BL_status.b0; //пін підсвічування виставляємо у відповідності зі значенням змінної, що зберігає стан підсвічування lcd_reg.DB4 = com.b4; // виставляємо на шину даних індикатора 4 старших біта нашої команди lcd_reg.DB5 = com.b5; lcd_reg.DB6 = com.b6; lcd_reg.DB7 = com.b7; lcd_reg.EN = 1; //ставимо строб. lcd_reg); // пишемо в регістр PCF8574, фактично відправивши дані на індикатор delay_us (300); // чекаємо тайммаут lcd_reg.EN = 0; lcd_reg = 0; lcd_reg.BL = BL_status.b0;lcd_reg.DB4 = com.b0;//те ж саме для 4 молодших біт lcd_reg.DB5 = com.b1; lcd_reg.DB6 = com.b2; lcd_reg.DB7 = com.b3; lcd_reg.EN = 1; I2C_PCF8574_WriteReg (lcd_reg); delay_us (300); lcd_reg.EN = 0; I2C_PCF8574_WriteReg (lcd_reg); delay_us (300); ) void LCD_CHAR (unsigned char com) // відправка індикатора даних (ASCII коду символу) (lcd_reg = 0; lcd_reg.BL = BL_status.b0; lcd_reg.EN = 1; lcd_reg.RS = 1; // відправка символу відрізняється від відправки команди установкою в 1 біта RS lcd_reg.DB4 = com.b4;// виставляємо на входах 4 старших біта lcd_reg.DB5 = com.b5; lcd_reg.DB6 = com.b6; delay_us(300);lcd_reg.EN=0;//скидаємо строб.імпульс в 0, індикатор читає дані I2C_PCF8574_WriteReg(lcd_reg);delay_us(300);lcd_reg=0;lcd_reg.BL 1; lcd_reg.RS = 1; lcd_reg.DB4 = com.b0; // виставляємо на входах 4 молодших біти lcd_reg.DB5 = com.b1; lcd_reg.DB6 = com.b2; lcd_reg.DB7 = com.b3; lcd_reg), delay_us (300); lcd_reg.EN = 0; I2C_PCF8574_WriteReg (lcd_reg); delay_us (300); //ініціалізуємо наш I2c модуль у МК delay_ms(200) lcd_Command(0x28); // Дисплей у режимі 4 біти за такт delay_ms(5); lcd_Command(0x08); // Відключаємо виведення даних на дисплей delay_ms (5); lcd_Command(0x01); //Очищаємо дисплей delay_ms(5); lcd_Command(0x06); //Включаємо автоматичне зсув курсора після виведення символу delay_ms (5); lcd_Command(0x0C); //Включаємо відображення інформації без відображення курсору delay_ms(25); ) void lcd_I2C_txt(char *pnt) //Виведення рядка символів на дисплей (unsigned short int i; //тимчасова змінна індексу масисва символів char tmp_str; //тимчасовий масив символів, довжиною на 1 більший за довжину рядка дисплея, так як рядок потрібно закінчити сиv символом NULL ASCII 0x00 strncpy(tmp_str, pnt, displenth);//копіюємо в наш тимчасовий рядок не більше displenth символів вихідного рядка for (i=0; i Тепер підключимо щойно створену бібліотеку у файлу з нашою головною функцією:

#include "i2c_lcd.h" //інклудим наш хедер-файл unsigned int i; //часова змінна лічильник void main() ( lcd_I2C_Init(); //ініціалізуємо дисплей lcd_I2C_backlight (1); //включимо підсвічування lcd_I2C_txt ("Hellow habrahabr"); //виведемо на дисплей рядку 1 lcd_I2C_Goto (2,1); //перейдемо до 1 символу 2 рядки lcd_i2c_int (i); //виведемо значення на дисплей i++; // інкриментуємо лічильник ) )

Якщо все правильно зібрано, то ми повинні побачити на індикаторі текст і лічильник, що інкриметується кожну секунду. Загалом, нічого складного:)

У наступній статті ми продовжимо розбиратися з протоколом i2c та пристроями працює з ним. Розглянемо роботу з EEPROM 24XX пам'яттю та акселерометром/гіроскопом MPU6050.

Сьогодні на нашому операційному столі новий гість, це продукт компанії Microchip, розширювач портів MCP23008-E. Призначена ця штуковина (як відомо з назви) збільшення числа I/O ніг мікроконтролера, якщо їх раптом стало бракувати. Звичайно якщо нам потрібні ноги-виходи можна взяти і не паритися. Якщо потрібні ноги-входи, то й тут є рішення на жорсткій логіці. Якщо ж нам потрібні одночасно входи та виходи та ще й керована підтяжка для входів, то розширювач портів це, мабуть, найбільш нормальне рішення. Що стосується ціни девайса то вона дуже скромна - приблизно долар. У цій статті я спробую детально описати як керувати цією мікросхемою за допомогою мікроконтролера AVR.

Для початку трохи про характеристики:

  • 8 незалежних пінів порту
  • Інтерфейс для зв'язку із зовнішнім світом - I2C (частота до 1.7 МГц)
  • Підтяжка для входів, що настроюється.
  • Може смикнути ногою коли стан певних входів зміниться
  • Три входи для встановлення адреси мікросхеми (можна повісити 8 пристроїв на одну шину)
  • Робоча напруга від 1.8 до 5.5 вольт
  • Малий струм споживання
  • Великий вибір корпусів (PDIP/SOIC/SSOP/QFN)

Інтерфейс я вважаю за краще використовувати I2C, він приваблює мене малою кількістю проводків:-) проте якщо потрібна дуже швидка швидкість (до 10 МГц), то потрібно використовувати SPI інтерфейс який присутній в MCP23S08. Різниця між MCP23S08 і MCP23008, як я зрозумів тільки в інтерфейсі і в кількості ніг для завдання адреси мікросхеми. Кому що до душі коротше. Розпинування мікрохи є в датасіті, вона ні чим не цікава і розглядатися тут не буде. Тому відразу перейдемо до того, як почати працювати з цим девайсом. Вся робота зводиться до того щоб записувати і зчитувати певні дані з регістрів мікросхеми. Регістрів на радість мені виявилося зовсім небагато — лише одинадцять штук. Писати та читати дані з регістрів дуже просто, пост про це . Йдеться там правда не про регістри, але принцип той самий. Тепер залишилося з'ясувати з яких регістрів що зчитувати і які регістри що записувати. У цьому нам, зрозуміло, допоможе даташит. З даташита ми дізнаємося, що мікросхема має такі регістри:

Реєстр IODIR
Задає напрямок, у якому йдуть дані. Якщо біт відповідний певній ніжці встановлений в одиницю, то вхід ніжка. Якщо скинуто у нуль, то вихід. Коротше цей регістр аналог DDRx в AVR (тільки в AVR 1 це вихід а 0 - вхід).

Реєстр IPOL
Якщо біт регістра встановлено, то відповідної ноги включена інверсія входу. Це означає, що якщо на ніжку подати балку. нуль то з регістра GPIO вважається одиниця і навпаки. Корисність цієї фічі дуже сумнівна.

Реєстр GPINTEN
Кожен біт цього регістру відповідає певному піну порту. Якщо біт встановлено, відповідний пін порту налаштований на вхід може викликати переривання. Якщо біт скинутий, то щоб не робили з ногою порту - переривання не буде. Умови виникнення переривання задаються двома наступними регістрами.

Реєстр DEFVAL
Поточне значення пінів налаштованих на вхід постійно порівнюється з цим регістром. Якщо раптом поточне значення стало відрізнятися від того, що в цьому регістрі, то виникає переривання. Простіше кажучи - якщо біт встановлений, то переривання буде виникати, коли на відповідній ніжці відбудеться зміна рівня з високого на низький. Якщо біт скинутий, то переривання виникає по наростаючому фронту.

Реєстр INTCON
Кожен біт цього регістру відповідає певному піну порту. Якщо біт скинутий, будь-яка зміна логічного рівня на протилежний викликає переривання. Якщо біт встановлений, то на виникнення переривання впливає регістр DEFVAL (інакше він взагалі ігнорується).

Реєстр IOCON
Це регістр налаштувань. Складається він із чотирьох біт:
SEQOP- біт керує автозбільшенням адреси. Якщо його встановлено, то автозбільшення вимкнено, інакше увімкнено. Якщо ми виключимо його, то можемо дуже швидко зчитувати значення того самого регістру за рахунок того, що нам не доведеться передавати його адресу щоразу. Якщо ж потрібно швидко прочитати всі 11 регістрів по черзі, то автозбільшення потрібно включити. При кожному зчитуванні байта з регістра адреса сама збільшуватиметься на одиницю і передавати її не доведеться.
DISSLW- Фіг знає че за біт. Як його не крутив, все одно працює все. Буду радий, якщо хтось пояснить.
HAEN- Біт налаштування виводу INT. Якщо він встановлений висновок налаштований як відкритий стік, якщо біт скинутий, то активний рівень на нозі INT визначає біт INTPOL
INTPOL- Визначає активний рівень на нозі INT. Якщо він встановлений то активний рівень одиниця, інакше нуль.

Є ще біт HAENале він не використовується в даній мікросхемі (включає/вимикає піни апаратної адресації MCP23S08)

Реєстр GPPU
Керує підтяжкою входів. Якщо біт встановлений, то на відповідному йому піні з'явиться підтяжка до живлення через резистор 100 кОм.

Реєстр INTF
Регістр прапорів переривань. Якщо біт встановлений це означає, що відповідна йому нога порту викликала переривання. Зрозуміло переривання для потрібних ніг повинні бути включені до регістру GPINTEN

Реєстр INTCAP
У момент виникнення переривання до цього регістру зчитується весь порт. Якщо потім після цього будуть ще переривання, то вміст цього регістру не затремтить новим значенням до тих пір, поки ми не вважаємо його або GPIO.

Реєстр GPIO
Зчитуючи дані з регістру – зчитуємо логічні рівні на ніжках порту. Записуючи дані – встановлюємо логічні рівні. При записі в цей регістр автоматично відбувається запис цих самих даних в регістр OLAT

Реєстр OLAT
Записуючи дані до цього регістру - виводимо дані у порт. Якщо прочитати звідти дані, то прочитається те, що записали, а не те, що фактично є на входах порту. Для читання входів використовуємо лише GPIO.

Опис регістрів на мій погляд цілком нормальне і мозок нікому підірвати не повинно. Тепер заради інтересу напишемо невеликий демонстраційний приклад який опитуватиме 4 кнопки підключені до розширювача порту, і в залежності від їх стану запалювати або гасити 4 відповідні світлодіоди підключені до того ж розширювача. Приклад буде максимально простим, без жодних переривань. Просто постійно зчитуємо стан кнопок та виводимо цей стан на світлодіоди. Схема нашого пристрою виглядатиме так:

Висновок переривання можна не підключати, він тут нам не потрібен, а ось підтяжка для скидання обов'язкова! Я витратив багато часу, поки зрозумів, що розширювач портів у мене періодично скидався через відсутність підтяжки. Тепер візьмемося за код. Писати будемо звичайно на . Код простецький:

Program rashiritel; const AdrR=%01000001; //Адреса мікросхеми з бітом читання const AdrW=%01000000; //Адреса мікросхеми з бітом запис var r:byte; ///Процедура записує дані із змінної Dat в регістр за адресою Adr Procedure WriteReg(Dat,Adr:byte); Begin TWI_Start(); TWI_Write(AdrW); TWI_Write(Adr); TWI_Write(Dat); TWI_Stop(); End; ///Функція повертає значення регістру на адресу Adr Function ReadReg(Adr:byte):byte; var a:byte; Begin TWI_Start(); TWI_Write(AdrW); TWI_Write(Adr); TWI_Start(); TWI_Write(AdrR); a:=TWI_Read(0); TWI_Stop(); result:=a; End; begin TWI_INIT(200000); ///Ініціалізація i2c WriteReg(%00001111,0x00); //Молодші 4 біта входи, а інші 4 виходи WriteReg(%00001111,0x06); // Увімк. підтяжку для 4 входів While TRUE do Begin r:=ReadReg(0x09); //Вважали стан входів r:= NOT r; //Треба інвертувати біти інакше при відпущеній кнопці світлодіоди горітимуть r:= r shl 4; //Зсуваємо на 4 біти вліво... WriteReg(r,0x0A); //Виводимо стан кнопок end; end.

Опубликовано 26.10.2016

У попередній статті ми розглянули роботу STM32 з шиною I 2 C як Майстер. Тобто він був ведучий і опитував датчик. Тепер зробимо так, щоб STM32 був Slave і відповідав на запити, тобто сам працював як датчик. Ми виділимо 255 байт пам'яті під регістри з адресами від 0 до 0xFF і дозволимо Майстеру в них писати/читати. А щоб приклад був не таким простим, зробимо з нашого STM32, ще й аналого-цифровий перетворювач з інтерфейсом I 2 C. ADC оброблятиме 8 каналів. Результати перетворень контролер віддаватиме Майстру під час читання з регістрів. Оскільки результат перетворення ADC займає 12 біт, нам знадобиться 2 регістри (2 байти) за кожен канал ADC.

i2c_slave.hмістить налаштування:

I2CSLAVE_ADDR– адреса нашого пристрою;

ADC_ADDR_START- Початкова адреса регістрів, які відповідають за результати перетворень ADC.

У файлі i2c_slave.cнас найбільше цікавлять функції get_i2c1_ramі set_i2c1_ram. Функція get_i2c1_ramвідповідає за зчитування даних із регістрів. Вона повертає дані із вказаної адреси, які віддаються Майстрові. У нашому випадку дані зчитуються з масиву i2c1_ramАле якщо Майстер запитує адреси регістрів з діапазону відведеного для результатів ADC, то надсилаються дані перетворень ADC.

get_i2c1_ram:

Uint8_t get_i2c1_ram(uint8_t adr) ( // ADC data if ((ADC_ADDR_START<= adr) & (adr < ADC_ADDR_START + ADC_CHANNELS*2)) { return ADCBuffer; } else { // Other addresses return i2c1_ram; } }

Функція set_i2c1_ram– записує дані прийняті від Майстра до регістрів із зазначеною адресою. У нашому випадку дані просто записуються в масив i2c1_ram. Але це необов'язково. Ви можете, наприклад, додати перевірку, і коли на певну адресу приходить певна кількість, виконати якісь дії. Таким чином Ви зможете подавати мікроконтролеру різні команди.

set_i2c1_ram:

Void set_i2c1_ram(uint8_t adr, uint8_t val) ( i2c1_ram = val; return; )

Ініціалізація досить проста:

Int main(void) ( SetSysClockTo72(); ADC_DMA_init(); I2C1_Slave_init(); while(1) ( ) )

Спочатку ми встановлюємо максимальну частоту роботи контролера. Максимальна швидкість необхідна, коли потрібно уникнути затримок на шині I 2 C. Потім запускаємо роботу ADC з використанням DMA. Про. Про. І, нарешті, виконуємо ініціалізацію шини I 2 C як Slave. Як бачите, нічого складного.

Тепер підключимо наш модуль STM32 до Raspberry Pi. До каналів ADC підключимо потенціометри. І зчитатимемо з нашого контролера показники ADC. Не забуваємо, що для роботи шини I 2 C потрібно на кожну лінію шини встановити резистори, що підтягують.

У консолі Raspberry перевіримо чи видно наш пристрій на шині I 2 C (про те, ):

I2cdetect -y 1

Як бачите, адреса пристрою 0x27хоча ми вказали 0x4E. Коли буде час, подумайте, чому так сталося.

Для зчитування з регістрів I 2 C-Slave пристрою виконуємо команду:

I2cget -y 1 0x27 0x00

Де:
0x27- адреса пристрою,
0x00– адреса регістру (0x00…0xFF).

Для запису в регістри I 2 C-Slave пристрою виконуємо команду:

I2cset -y 1 0x27 0xA0 0xDD

Де:
0x27- адреса пристрою,
0xA0– адреса регістру
0xDD-8-bit дані (0x00…0xFF)

Попередня команда записала число 0xDD у регістр 0xA0(писати в перші 16 регістрів можна, і сенсу немає, за якими вони відведені під ADC). Тепер прочитаємо:

I2cget -y 1 0x27 0xA0

Щоб спростити процес зчитування даних ADC-каналів, я написав скрипт:

#!/usr/bin/env python import smbus import time bus = smbus.SMBus(1) address = 0x27 while (1): ADC = (); for i in range(0, 8): LBS = bus.read_byte_data(address, 0x00+i*2) MBS = bus.read_byte_data(address, 0x00+i*2+1) ADC[i] = MBS*256 + LBS print ADC time.sleep(0.2)

Він опитує та виводить у консоль результати всіх 8-ми ADC-каналів.

Аналогічним чином можна поєднати кілька мікроконтролерів. Один з них має бути Master (), інші Slave.

Бажаю успіхів!




Top