HHIMERA писал(а):Заодно не заморачиваться с вечно задротным И2Ц... с его ерратами и глюками...
Я немножко заморочился и для себя определил, что на f030 мастер в AUTOEND=1 работает примерно так, как и должен, чего у меня не получилось добиться, что с бубном, что без, если старт и стоп формировать руками. Я может не сильно вкуривался, но углядев, что AUTOEND=1 работает во всех моих случаях так, как надо, просто не стал даже запариваться с ручным формированием состояний.
Итак, камень STM32F030C6T6 проиничен на штатные 48мгц тактовой. Для того, чтобы гененрировать сигналы шины I2C с правильными таймингами надо в регистр I2C1->TIMINGR занести правильное значение, которое надлежит вычислить по некой совершенно зубодробительной методике, чем я решительно не пожелал заниматься. Правильное значение я взял из снайпетов, где, надеюсь, оно было вычислено без ошибок.
Вобщем, для работы шины на 400кгц весь инит выглядит так:
Код: Выделить всё
__INLINE void initI2C1(void) {
I2C1->CR1 = I2C_CR1_PE; // enable I2C
I2C1->TIMINGR = (uint32_t)0x00B01A4B; // Timing register value is computed with the AN4235 xls file, fast Mode @400kHz with I2CCLK = 48MHz, rise time = 140ns, fall time = 40ns
}
Определим возможные коды ошибок (здесь они выбраны произвольно и имеют разные значения только затем, чтобы отличать их друг от друга), которые могут произойти по разным причинам при работе с I2C:
Код: Выделить всё
#define ERROR_I2C_TX_TIMEOUT (uint32_t) (1<<16)
#define ERROR_I2C_RX_TIMEOUT (uint32_t) (1<<17)
#define ERROR_I2C_STOP_TIMEOUT (uint32_t) (1<<18)
#define ERROR_I2C_BUS_FAIL (uint32_t) (1<<19)
Заодно переменную, в которую они станут заноситься, если вдруг возникнут:
globalError -- это глобальная переменная, объявленная где-то выше. Имя может быть любым, главное, чтобы это было упоминание реальной переменной.
Для отслеживания таймаута, чтобы не зациклиться навечно в ожидании какого-нибудь флага, сошлемся на какой-нибдуь счетчик (по тому же принципу, как с I2C_ERROR_VAR), который тупо декрементируется где-нибудь в обработчике SysTick-а раз в микросекунду.
Код: Выделить всё
#define I2C_TIMEOUT_COUNTER_VAR timeoutCounter
Макросы установки и сброса счетчика таймаута:
Код: Выделить всё
#define I2C_SET_TIMEOUT(TIMEOUT) __disable_interrupt(); I2C_TIMEOUT_COUNTER_VAR = TIMEOUT; __enable_interrupt()
#define I2C_RESET_TIMEOUT() __disable_interrupt(); I2C_TIMEOUT_COUNTER_VAR = 0; __enable_interrupt()
Дальше есть смысл определить макросы, которые можно использовать внутри функций для разнообразных манипуляций с I2C. Вот макрос, который начинает всякие операции записи по I2C. Он принимает три параметра: 1) i2c-адрес устройства, к которому планируется обращение, 2) общее число байт, которое будет передано по шине i2c в течение данного сеанса, 3) значение байта, который пойдет первым (обычно оно еще называется номером регистра) в данном сеансе.
Код: Выделить всё
#define I2C_START_WRITING(ADDR, COUNT, VALUE) \
do { \
I2C1->TXDR = VALUE; \
I2C1->CR2 \
= I2C_CR2_AUTOEND \
| ((COUNT) << 16) \
| ((ADDR) << 1) \
| I2C_CR2_START;\
} while (0)
Такой же макрос, но для операций чтения. Параметры, которые он принимает чуть отличаются от I2C_START_WRITING в том, что нет значения регистра, к которому происходит обращение. Чтение начинается с текущей позиции. Если необходимо чтение с какой-то иной позиции, то перед ним нужно будет записать один байт номера регистра с помощью I2C_START_WRITING.
Код: Выделить всё
#define I2C_START_READING(ADDR, COUNT) \
do { \
I2C1->CR2 \
= I2C_CR2_AUTOEND \
| ((COUNT) << 16) \
| I2C_CR2_RD_WRN \
| ((ADDR) << 1) \
| I2C_CR2_START; \
} while (0)
Три макроса ожидания флага завершения операции. Первый для подтверждения окончания всего сеанса, второй для подтверждения окончания текущей операции чтения, а последний то же самое, но для записи:
Код: Выделить всё
#define I2C_WAIT_FOR_STOP_FLAG() \
while(!(I2C1->ISR & I2C_ISR_STOPF)) { \
if (I2C_TIMEOUT_COUNTER_VAR == 0) { \
I2C_ERROR_VAR |= ERROR_I2C_STOP_TIMEOUT; \
return 0; \
} \
} \
I2C1->ICR = I2C_ICR_STOPCF
#define I2C_WAIT_FOR_RX_FLAG() \
while(!(I2C1->ISR & I2C_ISR_RXNE)) { \
if (I2C_TIMEOUT_COUNTER_VAR == 0) { \
I2C_ERROR_VAR |= ERROR_I2C_RX_TIMEOUT; \
return 0; \
} \
}
#define I2C_WAIT_FOR_TX_FLAG() \
while(!(I2C1->ISR & I2C_ISR_TXE)) { \
if (I2C_TIMEOUT_COUNTER_VAR == 0) { \
I2C_ERROR_VAR |= ERROR_I2C_TX_TIMEOUT; \
return 0; \
} \
}
Макросы отслеживают истечение таймаута текущей операции и прерывают выполнение функции, в которой они вызывались, если таймаут таки случился.
В принципе, данных макросов достаточно, чтобы писать разнообразные функции по работе с I2C, что ниже я покажу на примере работы с той же AT24C32.
Код: Выделить всё
#define I2C_TIMEOUT 50
#define AT24C32_ADDRESS ((uint8_t)0x57)
bool at24c32_read(uint16_t addr, uint8_t buffer[], uint8_t count) {
I2C_SET_TIMEOUT(I2C_TIMEOUT);
I2C_START_WRITING(AT24C32_ADDRESS, 2, addr >> 8);
I2C_WAIT_FOR_TX_FLAG();
I2C1->TXDR = (addr & 0x00FF); // Byte to send
I2C_WAIT_FOR_STOP_FLAG();
I2C_START_READING(AT24C32_ADDRESS, count);
for (uint8_t i = 0; i < count; i++) {
I2C_WAIT_FOR_RX_FLAG();
buffer[i] = I2C1->RXDR;
}
I2C_WAIT_FOR_STOP_FLAG();
I2C_RESET_TIMEOUT();
return true;
}
bool at24c32_write(uint16_t addr, uint8_t buffer[], uint8_t count) {
I2C_SET_TIMEOUT(I2C_TIMEOUT);
I2C_START_WRITING(AT24C32_ADDRESS, count + 2, addr >> 8);
I2C_WAIT_FOR_TX_FLAG();
I2C1->TXDR = (addr & 0x00FF); // Byte to send
for (uint8_t i = 0; i < count; i++) {
I2C_WAIT_FOR_TX_FLAG();
I2C1->TXDR = buffer[i];
}
I2C_WAIT_FOR_STOP_FLAG();
I2C_RESET_TIMEOUT();
return true;
}
Функции возвращают true/false, как результат всей операции. Если true, то можно ехать дальше. Если false, то надо глядеть переменную globalError и выяснять, что пошло не так. Функции не идеальные и не годятся на все случаи жизни. У меня они используются для чтения/записи блоков по 32 байта и в таком виде работают без нареканий. Для больших размерностей единичной операции стоит уточнить особенности постраничной записи конкретного чипа EEPROM и внести соответствующие правки в алгоритм. Приведенный здесь код является частью более обширной моей писанины и хочется надеяться, что выдрал я его без ошибок. Написано для компилятора IAR.