Я тоже ничего не понял про ACK во время START... Вообще таких вещей протокол не предусматривает. Есть четкая последовательность выдачи сигналов на линии. Да, бывают сбои по различным причинам - от мощных переключателей вне устройства, например, происходит рассинхронизация приемника и передатчика путем вставки "лишнего" clock-а.
Я, например, всегда придерживаюсь стратегии следующего рода: если процессор очень шустрый, периферия медленная, но есть DMA - я всегда работаю с DMA.
I2C в STM32F429 также поднимал с обработкой всех ошибок. Я делал так:
CODE
// HW_ExchangeTransmitData - функция отправки данных по внешнему каналу обмена.
// Параметры:
// Buffer - указатель на буфер передаваемых данных;
// Size - объем данных в байтах.
// Возвращаемое значение:
// HW_EXCHANGE_TRANSMIT_OK - отправка успешно запущена;
// HW_EXCHANGE_TRANSMIT_ERROR_TIMEOUT - канал обмена занят транзакцией данных.
unsigned int HW_ExchangeTransmitData(void *Buffer, unsigned int Size)
{
HW_EXCHANGE_TIMER_START(65535);
while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY) && !ExchangeBusyStatus);
HW_EXCHANGE_TIMER_STOP();
unsigned int PhysicalBusy = ExchangeBusyStatus;
ExchangeBusyStatus = 0;
if(PhysicalBusy)
return HW_EXCHANGE_TRANSMIT_ERROR_TIMEOUT;
else
{
ExchangeTxDataQuantity = Size;
DMA_MemoryTargetConfig(DMA1_Stream6, (unsigned int)Buffer, DMA_Memory_0);
DMA_SetCurrDataCounter(DMA1_Stream6, ExchangeTxDataQuantity);
I2C_GenerateSTART(I2C1, ENABLE);
HW_EXCHANGE_TIMER_START(1);
return HW_EXCHANGE_TRANSMIT_OK;
}
}
При отправке я сначала смотрю, а не занят ли модуль I2C прошлой транзакцией. Чтобы вечно не тупить в цикле ожидания BUSY, покрываю проверку аппаратным таймером на максимальную длину посылки через DMA. Если занят - говорю что вышел таймаут ожидания и выхожу из функции. Чуть позже добавлю сюда просто ожидание семафора, чтобы освободить задачу (эта функция крутится в составе ОСРВ).
После того как I2C освободился, заполняю служебные переменные размера передаваемого сообщения, настраиваю DMA, но не включаю его. Формирую условие START и запускаю контролирующий таймер на минимальный интервал передачи одного байта. Теперь выхожу из функции - вся остальная работа будет сделана в прерывании.
CODE
void I2C1_EV_IRQHandler(void)
{
if(I2C_GetITStatus(I2C1, I2C_IT_SB) == SET)
{
ExchangeDirection = HW_EXCHANGE_DIRECTION_TRANSMITTER;
HW_EXCHANGE_TIMER_STOP();
I2C_Send7bitAddress(I2C1, HW_EXCHANGE_I2C_ADDRESS_ABONENT, I2C_Direction_Transmitter);
HW_EXCHANGE_TIMER_START(1);
return;
}
if(ExchangeDirection == HW_EXCHANGE_DIRECTION_RECEIVER)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if(I2C_GetITStatus(I2C1, I2C_IT_ADDR) == SET)
{
if(!ExchangeOverrunStatus)
{
ExchangeOverrunStatus = 1;
DMA_MemoryTargetConfig(DMA1_Stream0, ExchangeRxDataBuffer, DMA_Memory_0);
DMA_SetCurrDataCounter(DMA1_Stream0, ExchangeRxBufferSize);
DMA_ClearFlag(DMA1_Stream0, DMA_FLAG_TCIF0);
DMA_Cmd(DMA1_Stream0, ENABLE);
}
else
System.ExchangeStatus |= SYSTEM_EXCHANGE_STATUS_RX_ERROR_DATA_OVERRUN;
(void)I2C_GetFlagStatus(I2C1, I2C_FLAG_TRA);
}
if(I2C_GetITStatus(I2C1, I2C_IT_BTF) == SET)
{
(void)I2C_ReceiveData(I2C1);
System.ExchangeStatus |= SYSTEM_EXCHANGE_STATUS_RX_ERROR_BUFFER_OVERFLOW;
}
if(I2C_GetITStatus(I2C1, I2C_IT_STOPF) == SET)
{
if(DMA_GetCmdStatus(DMA1_Stream0) == ENABLE)
DMA_Cmd(DMA1_Stream0, DISABLE);
if(I2C_GetFlagStatus(I2C1, I2C_FLAG_RXNE) == SET)
{
(void)I2C_ReceiveData(I2C1);
System.ExchangeStatus |= SYSTEM_EXCHANGE_STATUS_RX_ERROR_BUFFER_OVERFLOW;
}
*ExchangeRxDataQuantity = ExchangeRxBufferSize - DMA_GetCurrDataCounter(DMA1_Stream0);
xSemaphoreGiveFromISR(ExchangeRxSemaphoreHandle, &xHigherPriorityTaskWoken);
I2C_GenerateSTOP(I2C1, DISABLE);
}
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
else
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if(I2C_GetITStatus(I2C1, I2C_IT_ADDR) == SET)
{
HW_EXCHANGE_TIMER_STOP();
DMA_ClearFlag(DMA1_Stream6, DMA_FLAG_TCIF6);
DMA_Cmd(DMA1_Stream6, ENABLE);
(void)I2C_GetFlagStatus(I2C1, I2C_FLAG_TRA);
HW_EXCHANGE_TIMER_START(ExchangeTxDataQuantity + 1);
}
else if(I2C_GetITStatus(I2C1, I2C_IT_BTF) == SET)
{
HW_EXCHANGE_TIMER_STOP();
I2C_GenerateSTOP(I2C1, ENABLE);
ExchangeDirection = HW_EXCHANGE_DIRECTION_RECEIVER;
xSemaphoreGiveFromISR(ExchangeTxSemaphoreHandle, &xHigherPriorityTaskWoken);
}
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
В прерывании обрабатываются все события I2C в зависимости от приема или передачи, при этом каждый шаг контролируется защитным временным интервалом в зависимости от количества передаваемых данных.
Таким образом, все сошлось к следующему:
1. Настроить DMA.
2. Выдать на линию START-условие.
3. Ждать в прерывании установки события START (SB).
4. Запустить передать физический адрес устройства.
5. Ждать в прерывании установки события окончания передачи адреса (ADDR).
6. Запустить DMA.
7. Ожидать в прерывании установки события BTF.
8. Выдать на линию STOP-условие.
На почти каждый шаг накладывается штрафной таймер, контролирующий выполнение текущего шага.
Этот код работает очень долго и стабильно. Обработка ошибок возложена на вышестоящие функции, как видно, в системный журнал ошибок (структура System) постоянно сваливаются нужные флаги.