‮Сдвиг по фазе (kincajou) wrote,
‮Сдвиг по фазе
kincajou

worklog: STM32F746 I2C через DMA.

Медленно думаю, много сплю, мрачная погода.
Решил, что надо себя развлечь и вернулся к ковырянию кода.
Всё готово к тому, чтобы перейти на обработку прерываниями и затем добавить синхронизацию через РТОС.
Итак, как выглядит передача через порт I2C1 (жёстко прибит гвоздями, т.к. именно он используется в схеме Awesome Board, но при желании это всё можно "параметризировать").


  1. перед началом работы модуля I2C его вообще желательно встряхивать в начальное состояние (параноидальный метод!):
    	I2C1->CR1 &= ~(1 << I2C_CR1_PE_Pos);
    	I2C1->CR1 |= (1 << I2C_CR1_PE_Pos);
    


  2. модуль DMA должен быть проинициализирован перед тем, как мы начнём передачу (хоть это на самом деле и не строго-обязательно, но так всё-таки разумнее):
    	DMA1_Stream6->CR = 0;
    	DMA1_Stream6->CR =
    		(0x01 << DMA_SxCR_CHSEL_Pos) | // канал
    		(0x01 << DMA_SxCR_PL_Pos) | // приоритет
    		(0x00 << DMA_SxCR_MSIZE_Pos) | // грануляция памяти 8 бит
    		(0x00 << DMA_SxCR_PSIZE_Pos) | // грануляция периферии 8 бит
    		(1 * DMA_SxCR_MINC) | // инкремент памяти 1 байт
    		(0 * DMA_SxCR_PINC) | // инкремент периферии отсутствует
    		(0x01 << DMA_SxCR_DIR_Pos ); // направление ПАМЯТЬ->ПЕРИФЕРИЯ
    
    	DMA1_Stream6->M0AR = (uint32_t)txbuf; /* адрес в памяти */
    	DMA1_Stream6->PAR = (uint32_t)&I2C1->TXDR; /* адрес периферии */
    	DMA1_Stream6->NDTR = txcount; // сколько порций данных передать
    
    	__DMB(); SCB_CleanDCache_by_Addr ((uint32_t*)txbuf, txcount); /* когерентность кэша */
    


  3. прописываем адрес подчинённого устройства и параметры передачи; врубаем передачу
    	I2C1->CR2 =
    		(0 << I2C_CR2_ADD10_Pos) | /* адресация 7 бит */
    		((addr & 0xFE) << I2C_CR2_SADD_Pos) | /* адрес устройства (внимание!) */
    		(0 << I2C_CR2_RD_WRN_Pos) | /* направление передачи 0 (ЗАПИСЬ) */
    		(txcount << I2C_CR2_NBYTES_Pos) | /* кол-во байт */
    		(0 << I2C_CR2_AUTOEND_Pos); // управление состоянием STOP/RESTART: 0 программное (см.RM), 1 автоматическая выдача STOP
    	I2C1->CR1 |= I2C_CR1_TXDMAEN; // разрешаем работу с DMA TX
    	I2C1->CR2 |= I2C_CR2_START; /* запускаем передачу состояния START и адреса */
    


  4. а вот тут можно уже сделать обработку по прерыванию, но пока что сделано тупо ожидание:
    	while (!(I2C1->ISR & (I2C_ISR_TXIS | I2C_ISR_NACKF))) {}; /* ждём либо флаг TXIS, либо получение NACK */
    


  5. ..за которым следует развилка и ещё одно ожидание, вопиющее "вынеси меня в прерывание!!!":
    	if ((I2C1->ISR & I2C_ISR_NACKF)) { /* нам никто не ответил? */
    		I2C1->ICR |= I2C_ICR_NACKCF; /* чистим флаг */
    		//DMA1_Stream6->CR &= ~DMA_SxCR_EN;
    		I2C1->CR1 &= ~(1 << I2C_CR1_PE_Pos); /* выключаем I2C */
    		return -1; /* выходим с ошибкой */
    	};
    	DMA1_Stream6->CR |= DMA_SxCR_EN; /* НЕ сбрасывая флаг TXIS, включаем DMA */
    	while (!(DMA1->HISR & DMA_HISR_TCIF6)) {}; /* и дожидаемся окончания передачи */
    	DMA1->HIFCR |= DMA_HIFCR_CTCIF6; /* чистим флаг TCI */
    	I2C1->CR1 &= ~I2C_CR1_TXDMAEN; /* отключаем событие DMA TX * p.966; p.218 */
    	DMA1_Stream6->CR &= ~DMA_SxCR_EN; /* глушим поток DMA */
    



Если после передачи в I2C устройство не следует чтение, то надо сгенерить состояние STOP, дождаться высвобождения шины и выключить модуль. Но это уже совсем другая история Чтение данных организовано почти точно так же, различаются только биты направления передачи и номер потока DMA.
Subscribe
  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

  • 2 comments