Перейти к содержанию

Программный I2C для STM32


G1KuL1N

Рекомендуемые сообщения

В общем понадобилось тут подвесить FRAM к STM32, а пины аппаратных I2C были уже заняты, ну решил не менять пины, а добавить себе на вооружение на всякий, программный I2C. То что удалось нагуглить либо работало криво, либо приходилось допиливать и допиливать. Ну и написал свой с блекджеком и ш.. Так как в сети примеров мало программного I2C на STM32 то решил поделиться. Вдруг кому пригодится. Работает с библиотекой HAL, поправить нужно только под  свой камень и свои порты и все работает.

I2C.h

#ifndef __I2C_H_H__
#define __I2C_H_H__
// G1KuL1N
// ПОДКЛЮЧИТЬ БИБЛИОТЕКУ СВОЕГО КОНТРОЛЛЕРА

#include "stm32f4xx_hal.h"

// В CUBE MX порты I2C настроить на выход (либо в main.c вручну подать тактирование на нужны GPIO) 

//---подключение шины к пинам-----------------------------------------------------
#define SCL_I HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_15);   
#define SDA_I HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_14);
#define SCL_O HAL_GPIO_WritePin(GPIOC, GPIO_PIN_15, GPIO_PIN_RESET);
#define SDA_O HAL_GPIO_WritePin(GPIOC, GPIO_PIN_14, GPIO_PIN_RESET);
//--------------------------------------------------------------------------------
void i2c_init (void);               // Инициализация шины
void i2c_start_cond (void);        // Генерация условия старт
void i2c_restart_cond (void);      // Генерация условия рестарт
void i2c_stop_cond (void) ;       // Генерация условия стоп  
uint8_t i2c_send_byte (uint8_t data) ;      //Передать байт (вх. аргумент передаваемый байт) (возвращает 0 - АСК, 1 - NACK) 
uint8_t i2c_get_byte (uint8_t last_byte) ;  //Принять байт (если последний байт то входной аргумент = 1, если будем считывать еще то 0)(возвращает принятый байт)
//--------------------------------------------------------------------------------
// ПРИМЕР ИСПОЛЬЗОВАНИЯ
//=========================================================================================
//   Запись uint16_t во внешнюю еепром (FRAM FM24CL64) или любой другой 24LC памяти, 
//	 две ячейки,  указывается адрес первой ячейки, следующая идет adr++
//=========================================================================================
//
//void FRAM_W_INT(uint16_t adr, uint16_t dat){
//i2c_start_cond ();
//i2c_send_byte (0xA2); //адрес чипа + что будем делать (записывать)
//i2c_send_byte    ((adr & 0xFF00) >> 8);  
//i2c_send_byte    (adr & 0x00FF);
//i2c_send_byte    ((dat & 0xFF00) >> 8);  
//i2c_send_byte    (dat & 0x00FF);
//i2c_stop_cond();
//}

//=========================================================================================
//   Считывание uint16_t из внешней еепром (FRAM FM24CL64) или любой другой 24LC памяти, 
//	 две ячейки,  указывается адрес первой ячейки, следующая идет adr++
//=========================================================================================
//uint16_t FRAM_R_INT(uint16_t adr){
//uint16_t dat;
//i2c_start_cond ();
//i2c_send_byte (0xA2);
//i2c_send_byte    ((adr & 0xFF00) >> 8);  
//i2c_send_byte    (adr & 0x00FF);
//i2c_restart_cond ();
//i2c_send_byte (0xA3);
//dat =  i2c_get_byte(0);	
//dat <<= 8; 
//dat |= i2c_get_byte(1);
//i2c_stop_cond();
//return dat;
//}

// G1KuL1N

#endif /* __I2C_H_H__ */

i2C.c

#include "I2C.h"

volatile uint8_t i2c_frame_error=0; 

//-----------------------------------------------------------
__STATIC_INLINE void Delay_us (uint32_t __IO us) //Функция задержки в микросекундах us
{
us *=(SystemCoreClock/1000000)/5;
	while(us--);
}

//----------------------------------------------------
void SCL_in (void) //функция отпускания SCL в 1, порт на вход (необходимо установить используемый порт) 
	{
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Pin = GPIO_PIN_15;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
	}
//----------------------------------------------------
void SCL_out (void) //функция притягивания SCL в 0 (необходимо установить используемый порт) 
	{
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Pin = GPIO_PIN_15;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
	SCL_O;
	}
//----------------------------------------------------
void SDA_in (void) //функция отпускания SDA в 1, порт на вход (необходимо установить используемый порт) 
	{
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Pin = GPIO_PIN_14;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
	}
//----------------------------------------------------
void SDA_out (void) //функция притягивания SDA в 0 (необходимо установить используемый порт) 
	{
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Pin = GPIO_PIN_14;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
	SDA_O
	}

//----------------------------------------------------
void i2c_stop_cond (void)  // функция генерации условия стоп 
{
    uint16_t SCL, SDA;
		SCL_out(); // притянуть SCL (лог.0)
    Delay_us(10);
    SDA_out(); // притянуть SDA (лог.0)
    Delay_us(10);

    SCL_in(); // отпустить SCL (лог.1)
    Delay_us(10);
    SDA_in(); // отпустить SDA (лог.1)
    Delay_us(10);
    		
    // проверка фрейм-ошибки
    i2c_frame_error=0;		// сброс счётчика фрейм-ошибок
    SCL=SCL_I;
		SDA=SDA_I;
		if (SCL == 0) i2c_frame_error++;   // проберяем, чтобы на ноге SDA была лог.1, иначе выдаём ошибку фрейма
    if (SDA == 0) i2c_frame_error++;   // проберяем, чтобы на ноге SCL была лог.1, иначе выдаём ошибку фрейма
    Delay_us(40);
   }

void i2c_init (void) // функция инициализации шины
{
    i2c_stop_cond();   // стоп шины
    i2c_stop_cond();   // стоп шины
}
//----------------------------------------------------
void i2c_start_cond (void)  // функция генерации условия старт
{
		SDA_out(); // притянуть SDA (лог.0)
    Delay_us(10);
    SCL_out(); // притянуть SCL (лог.0)
    Delay_us(10);
}
//----------------------------------------------------
void i2c_restart_cond (void)   // функция генерации условия рестарт
{
    SDA_in(); // отпустить SDA (лог.1)
    Delay_us(10);
    SCL_in(); // отпустить SCL (лог.1)
    Delay_us(10);
    SDA_out(); // притянуть SDA (лог.0)
    Delay_us(10);
    SCL_out(); // притянуть SCL (лог.0)
    Delay_us(10);
}
//----------------------------------------------------
uint8_t i2c_send_byte (uint8_t data)  // функция  отправки байта  
{   
 uint8_t i;
 uint8_t ack=1;           //АСК, если АСК=1 – произошла ошибка
uint16_t SDA;   
	for (i=0;i<8;i++)
    {
        if (data & 0x80) 
				{
				SDA_in(); // лог.1
        }
				else 
				{
				SDA_out(); // Выставить бит на SDA (лог.0
				}
        Delay_us(10);
        SCL_in();   // Записать его импульсом на SCL       // отпустить SCL (лог.1)
        Delay_us(10);
        SCL_out(); // притянуть SCL (лог.0)
        data<<=1; // сдвигаем на 1 бит влево
					
    }
    SDA_in(); // отпустить SDA (лог.1), чтобы ведомое устройство смогло сгенерировать ACK
     Delay_us(10);
    SCL_in(); // отпустить SCL (лог.1), чтобы ведомое устройство передало ACK
     Delay_us(10);
    SDA=SDA_I;
		if (SDA==0x00) ack=1; else ack=0;    // Считать ACK

    SCL_out(); // притянуть SCL (лог.0)  // приём ACK завершён

    return ack; // вернуть ACK (0) или NACK (1)   

}
//----------------------------------------------------
uint8_t i2c_get_byte (uint8_t last_byte) // функция принятия байта
{
 uint8_t i, res=0;
	uint16_t SDA;
    SDA_in(); // отпустить SDA (лог.1)

    for (i=0;i<8;i++)
    {
        res<<=1;
        SCL_in(); // отпустить SCL (лог.1)      //Импульс на SCL
        Delay_us(10);
				SDA_in();
				SDA=SDA_I;
				if (SDA==1) res=res|0x01; // Чтение SDA в переменную  Если SDA=1 то записываем 1
        SCL_out(); // притянуть SCL (лог.0)
        Delay_us(10);
    }

    if (last_byte==0){ SDA_out();} // притянуть SDA (лог.0)     // Подтверждение, ACK, будем считывать ещё один байт
    else {SDA_in();} // отпустить SDA (лог.1)                 // Без подтверждения, NACK, это последний считанный байт
    Delay_us(10);
    SCL_in(); // отпустить SCL (лог.1)
    Delay_us(10);
    SCL_out(); // притянуть SCL (лог.0)
    Delay_us(10);
    SDA_in(); // отпустить SDA (лог.1)

    return res; // вернуть считанное значение
}

 

Я собрал Маршалл из деталей телевизора Весна, а он звучит не как Маршалл, а как Весна. В чем может быть проблема?

Кто-то куёт Metal, а я паяю Industrial © G1KuL1N (А то уже по всему интернету растащили :)

Ссылка на комментарий
Поделиться на другие сайты

Реклама: ООО ТД Промэлектроника, ИНН: 6659197470, Тел: 8 (800) 1000-321

редко пригождается конечно, но иногда вполне себе решение. Плюс

Нужно делать то, что нужно. А то, что не нужно, делать не нужно. (С) Винни Пух

Ссылка на комментарий
Поделиться на другие сайты

20% скидка на весь каталог электронных компонентов в ТМ Электроникс!

Акция "Лето ближе - цены ниже", успей сделать выгодные покупки!

Плюс весь апрель действует скидка 10% по промокоду APREL24 + 15% кэшбэк и бесплатная доставка!

Перейти на страницу акции

Реклама: ООО ТМ ЭЛЕКТРОНИКС, ИНН: 7806548420, info@tmelectronics.ru, +7(812)4094849

Выбираем схему BMS для корректной работы литий-железофосфатных (LiFePO4) аккумуляторов

 Обязательным условием долгой и стабильной работы Li-FePO4-аккумуляторов, в том числе и производства EVE Energy, является применение специализированных BMS-микросхем. Литий-железофосфатные АКБ отличаются такими характеристиками, как высокая многократность циклов заряда-разряда, безопасность, возможность быстрой зарядки, устойчивость к буферному режиму работы и приемлемая стоимость. Но для этих АКБ, также как и для других, очень важен контроль процесса заряда и разряда, а специализированных микросхем для этого вида аккумуляторов не так много. Инженеры КОМПЭЛ подготовили список имеющихся микросхем и возможных решений от разных производителей. Подробнее>>

Реклама: АО КОМПЭЛ, ИНН: 7713005406, ОГРН: 1027700032161

Это решение "в лоб", но работает, просто и быстро, написано за 20 минут так как горело, и с ходу рабочего решения не нашел. Задержку на таймере конечно лучше но необязательно, для ценителей программного времени там переделать 10 минут на таймер (хотя тот кто считает такты никогда в здравом уме не  будет использовать программный  i2C, когда есть аппаратный :D). На HAL так как это встроено в большую программу которая работает с HAL. Понадобилось просто подвесить в проект FRAM но уже все было готово и переделывать было бы долго и нудно. 

Я собрал Маршалл из деталей телевизора Весна, а он звучит не как Маршалл, а как Весна. В чем может быть проблема?

Кто-то куёт Metal, а я паяю Industrial © G1KuL1N (А то уже по всему интернету растащили :)

Ссылка на комментарий
Поделиться на другие сайты

10 us - много. Практически все I2C-девайсы работают на тактовой в 100 КГц, так что можно раза в 2 поднимать частоту смело. Или задефайнить её, чтобы пользователь сам указывал.

Ссылка на комментарий
Поделиться на другие сайты

обычно для софтового i2c высокой скорости и не требуется. Но я думаю что не такая уж и проблема чуть допилить предложенное решение. А на HAL оно или нет, вообще никакой разницы. Да и сколько там HALа то? Записать пин, да прочитать. Переписать это на регистры 5 минут, да и того не стоит вообще

Изменено пользователем mail_robot

Нужно делать то, что нужно. А то, что не нужно, делать не нужно. (С) Винни Пух

Ссылка на комментарий
Поделиться на другие сайты

33 минуты назад, mail_robot сказал:

обычно для софтового i2c высокой скорости и не требуется

Прочтите, пожалуйста, ещё раз мой пост и попытайтесь понять, о чём я там написал.

Ссылка на комментарий
Поделиться на другие сайты

интересно сколько за время передачи пакета по i2c, F4 успеет прос..ть полезной работы на блокирующих Delay. Неужели нельзя столь медленный интерфейс реализовать на таймере. выдавливать биты в прерывании таймера, самый лучший вариант это битбандинг для размотки параллельных данных в последовательность бит.

Изменено пользователем MasterElectric
Ссылка на комментарий
Поделиться на другие сайты

Уважаемый MasterElectric я выше уже ответил по этому поводу, не спорю с таймером лучше, но если ценим процессорное время - юзаем аппаратный I2C, и нет больше никаких "самых лучших вариантов". Во-вторых когда мне понадобился срочно программный I2C, если-б я залез сюда на форум, наткнулся бы на  Ваш пост с программным I2C с таймерами и бит-бандингом, я бы включил его себе с мыслью: вот чел молодец какой,  выложил не поленился, спасибо ему. Так что как есть, так есть.

Я собрал Маршалл из деталей телевизора Весна, а он звучит не как Маршалл, а как Весна. В чем может быть проблема?

Кто-то куёт Metal, а я паяю Industrial © G1KuL1N (А то уже по всему интернету растащили :)

Ссылка на комментарий
Поделиться на другие сайты

Пины аппартаного I2C заняты плата разведена, FRAM навесом

Я собрал Маршалл из деталей телевизора Весна, а он звучит не как Маршалл, а как Весна. В чем может быть проблема?

Кто-то куёт Metal, а я паяю Industrial © G1KuL1N (А то уже по всему интернету растащили :)

Ссылка на комментарий
Поделиться на другие сайты

у человека в первом посте все конкретно расписано что и зачем и в каких случаях. Чего накинулись то? Узкая специальная задача, применение которой - раз в сто лет заткнуть аппаратную дырку

Нужно делать то, что нужно. А то, что не нужно, делать не нужно. (С) Винни Пух

Ссылка на комментарий
Поделиться на другие сайты

  • 7 месяцев спустя...
В 13.08.2018 в 10:55, G1KuL1N сказал:

В общем понадобилось тут подвесить FRAM к STM32

Спасибо! По твоему примеру , запустил у себя еепром 24с08 в связке с STM32F100!  

Что может быть лучше в радиоэлектронике, чем программирование микроконтроллеров ?

Ссылка на комментарий
Поделиться на другие сайты

  • 9 месяцев спустя...
Гость Алексей Ададуров

Столкнулся с тем, что в STM32F103С8T6 и STM32F10RBT6 при работе с I2C через HAL начинает глючить USB. Думаю попробовать прикрутить софтовую реализацию -- и нашел вашу. Большое спасибо!

Ссылка на комментарий
Поделиться на другие сайты

  • 2 месяца спустя...

Переплутал SDA/SCL линии в плате прототипа, а на исправление нужно много времени, пришлось искать обходной путь. Софтварная реализация спасла меня :)

Ссылка на комментарий
Поделиться на другие сайты

  • 3 года спустя...

В этом коде адрес передаётся полностью.

//------------------------------------------------
//#define EEP_SIZE 1024 // 24C08
#define EEP_SIZE 2048 // 24C16
#define ADDR_H_MASK ((EEP_SIZE - 1) >> 8)

void EEPROM_Write16(uint16_t addr, uint16_t data)
{
	uint8_t addr_h = addr >> 8;
	
	addr_h &= ADDR_H_MASK;// mask 2 kByte if eeprom 24C16
	addr_h <<= 1;// shift 1
	addr_h |= SLAVE_OWN_ADDRESS;// |= 0xA0
	addr_h |= I2C_REQUEST_WRITE;// |= 0x00
	// start
	i2c_start_cond();
	// send address
	i2c_send_byte(addr_h);	
	i2c_send_byte(addr & 0xFF);
	// send data
	i2c_send_byte(data >> 8);  
	i2c_send_byte(data & 0xFF);
	// stop
	i2c_stop_cond();
}
//------------------------------------------------
uint16_t EEPROM_Read16(uint16_t addr)
{
	uint16_t data_out;
	uint8_t buffer[2];
	uint8_t addr_h = addr >> 8;
	
	addr_h &= ADDR_H_MASK;// mask 2 kByte if eeprom 24C16
	addr_h <<= 1;// shift 1
	// start
	i2c_start_cond();
	// send address + request write
	i2c_send_byte((addr_h | SLAVE_OWN_ADDRESS | I2C_REQUEST_WRITE));
	i2c_send_byte(addr & 0xFF);
	// restart
	i2c_restart_cond ();
	// send request read
	i2c_send_byte((SLAVE_OWN_ADDRESS | I2C_REQUEST_READ));
	// read
	buffer[0] = i2c_get_byte(0x00);
	buffer[1] = i2c_get_byte(0x01);
	// stop
	i2c_stop_cond();
	
	data_out = (uint16_t)(buffer[0] << 8) + (uint16_t)(buffer[1]);
	return data_out;
}
//------------------------------------------------

 

Ссылка на комментарий
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.
Примечание: Ваш пост будет проверен модератором, прежде чем станет видимым.

Гость
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Ответить в этой теме...

×   Вставлено с форматированием.   Восстановить форматирование

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

Загрузка...
  • Последние посетители   0 пользователей онлайн

    • Ни одного зарегистрированного пользователя не просматривает данную страницу
  • Сообщения

    • Ловите, как раз на широкую полосу для Палника рисовали, автора не знаю. Все, что нужно.   
    • @Андрей0З9  Это что за учитель и где, такой по трудовому обучению, задает задачи по физике 10 класса.?!  Бред полный.
    • Румынский дядька - перфекционист-фенечник. Бисера и бусин - дофига, вот и ставит куда не попадя, в данном случае бусинками выставил единую высоту ряда конденсаторов. Подобное встречалось, когда на ножки впаиваемых элементов одевались короткие кембрики одинаковой высоты, чем задавалась единая высота монтажа. Ну видимо румынскому дядьке лень было нарезать кучу одинаковой мелочёвки, зато было вналичии много бисера..., и креативно и желаемого достиг. С уважением, Сергей. 
    • О, это очень полезные регистры! в 88 только GPIOR0 сохранил свои полезные свойства. использую их как флаги событий прерываний. для GPIOR0 адрес порта ввода-вывода 0х1Е, а значит к нему применяются команды cbi, sbi, sbic, sbis   ну и   in, out. Когда происходит прерывание, процессор переходит на адрес обработки прерывания, вот там-то мы и располагаем код: sbi   GPIOR0, 0     ;установить в 1 бит 0 в регистре GPIOR0 reti                        ;вернуться из прерывания   Без использования регистра GPIOR0, а с использованием обычного регистра код выглядел бы иначе: push   R0                          ;освобождаем регистр R0 для SREG и сохраняем его in        R0, SREG               ;сохраняем SREG в R0, все флаги операций текущей программы sbr     R23, 1<<0             ;выставляем флаг признака прерывания, например бит 0 в регистре R23 out    SREG, R0               ;восстанавливаем SREG, все флаги операций текущей программы pop   R0                          ;восстанавливаем значение R0 reti                                  ;вернуться из прерывания   Нетрудно заметить......!   А, да команда: sbr     R23, 1<<0 в идеале изменяет флаги в SREG, потому и такая длинная цепочка команд.
    • Сабсоник 3 порядка потом усилитель на Оу. Далее все на столе отстроить и все 
    • Вот и я думаю сделать на сдвоенном операционнике входной усилитель и сабсоник. 
    • Я всегда подозревал, что эта схема была содрана кЕтайцами с какого-то старого и хорошо известного (но не у нас) блока питания, и что в оригинале использовались именно 741 операционники. И вот тому подтверждение... Все равно те микросхемы и транзисторы что они используют в наборах, чаще всего подделки. Мне например, пришлось заменить D1047 транзистор что шел в наборе на пару таких же, но нормальных (выдраных с дохлого усилка). Транзистор из набора (маркировка явно "левая", без какого-либо намека на изготовителя) сильно грелся даже на 1,5А. Такой же транзистор D1047, но из усилка грелся раза в два меньше, да и маркировка "нормальная".  Подозреваю что и TL081 что в наборе идут тоже возможно что перемаркированные 741 (они супердешевые, сравнимо с 358). 741 операционники выпускали все кому не лень, аналогов было выпущено очень много за полвека.  Были и на плюс-минус 22в, надо смотреть конкретный даташит и производителя, даже от буквы в конце это зависит.  Например, есть такой аналог uA741 от ST ("микро-А741"). ua741-957400.pdf ua741.pdf MA741.PDF
  • Похожий контент

×
×
  • Создать...