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

Программный 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 пользователей онлайн

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

    • Все предложенные к рассмотрению источники питания работают примерно по одному принципу: сетевое напряжение выпрямляется, фильтруется (получаем чуть больше 300 вольт постоянного), затем преобразуется снова в переменное, но уже на частотах в несколько десятков килогерц, понижается на трансформаторе и снова выпрямляется. За счёт высокой частоты преобразования используется трансформатор на ферритовом, а не на стальном, сердечнике, гораздо меньших габаритов и стоимости. Минусы: значительное усложнение схемы блока и вероятность возникновения различных помех от него. Модули управления (кроме первого) также являются импульными преобразователями, с теми же достоинствами и недостатками. Если нужно по быстрому собрать некое подобие ЛБП, то уж лучше брать модуль вроде этого. Ну и блок питания к нему соответствующий. Но не очень понятно, какой практический опыт можно получить от соединения готовых модулей парой проводов.  
    • У меня больше всего вопросов вызвала необычная схема обеспечения отрицательного питания. Автор этой обстоятельной заметки пишет: For this supply to work correctly, the transformer must have a secondary voltage of at least 18V RMS.  Почему? Что будет не так с отрицательным питанием, если напряжение на трансформаторе будет меньше 18В?   https://tinyurl.com/23mlwxtt - я в простейшей эмуляции ставлю 12В пикового напряжения для трансформатора и на стабилитроне все как положено: -5.6В.
    • Согласен, очень криво объяснил. Это работа трёх вольтовой линии, просто на диод шотки сдвоенный, на один анод приходит сигнал напрямую с трансформатора, а на второй через дроссель. Вольт/деление 5 вольт в клетке, тайминг по моему 10 МС. Третья фотография это сигнал на катодах уровень земли ровно по центру экрана. Но все линии по итогу в порядке 3.3 в, 5, в, 12 в и -12 в. Нагрузить все линии не могу сразу ,так как тут же выгорают транзисторы (имеется нагрузка 250 ватт по 10 ампер на каждую линию за исключением-12в), поэтому нагружаю 3.3 вольтовую линию на 10 ампер,  подключаю переменный резистор 50 ватт на 15 ом на 5 вольтовую линию и постепенно довожу до той той картины с перекосом (это гдето  50 ватт общее). По поводу микросхемы, вверху имеется скрин где между импульсами проскакивает мини импульс, если так можно сказать, он проскакивает и на одной  и на второй ноге (7,8). Микросхема не tl 494, а lw4933/abx942.1/c9421646. Далее они приходят на базы транзисторов 945g  коллекторы этих транзисторов соединены с  выводами трансформатора. Просто схема типовая, легче мне кажется просто привести фото самого блока, для тех кто разбирается будет гораздо информативне.  Диод шотки по 12 вольтовой линии был подгоревший, заменил на донора. Приводить скрины не буду что бы не захламлять тему. В итоге, пока все так же, при достижении определенной нагрузки суммарно где-то 50 ватт, появляется этот "выброс и перекос". По этому имеются мысли на два варианта, это микросхема , этот мини импульс между периодами, на низкой нагрузке особо не влияет, но при достижении определенной приводит с самовозбуждению входной цепи и непроизвольному открытию транзистора нижнего плеча. Либо дело в "горячей части", плавающий дефект в обвязке силовых ключей.  Спасибо за ответ.
    • @Gomerchik а вы контролировали как меняется уровень сигнала на А1 ардуины?
    • Спасибо за совет. Автором данного проекта я не являюсь, мне нужно было воссоздать уличный датчик для метеостанции взамен пропавшего(( Из разного найденного в интернете этот проект работает с моей станцией Орегон (спасибо автору). В понедельник попробую последовать Вашему совету. Но все равно куча непоняток  как блин это работает)) Если дело в неправильной отправки команды, то как на это влияет подключение датчика температуры? Если совсем не подключать таймер, то передача идет один раз (как и прописано в программе), станция принимает и отображает, но минут через сколько-то естественно станция уже ни чего не показывает, но с таймером питание полностью не пропадает с ардуинки, но передача сигнала каким-то образом работает по таймеру.  В моем понимании данная команда подается один раз потому, что таймер должен отключать питание МК после передачи сигнала и каждые 43 сек снова подавать питание (так того требует станция).  Ардуино передает показания температуры отключается полностью и 43 секунды мк не работает.  Сейчас у меня питание пока сделано на подпитке от солнечной батареи, но пару пасмурных дней и аккумулятор съедается до отключения(
    • thickman Так и сделаю. Вытащу из бу БП.  Буду знать, как отличить. Благодарю. Заменил транзисторы на IRFB20N50K. Картина стала, совсем другой.  Похоже трудность не в драйвере, на момент подвозбуда, переходные процессы, в нем, завершены. Увеличил затворные резисторы до 50ом, стало немного лучше.  Не понятно, почему верхний ключ греется несколько сильнее. Возможно, стоит посмотреть ток в коллекторе.  Снабберные емкости временно удалил, изменений не произошло.  Замена ТГР на другой, на кольце MSTN-16A-TH, так же, результата не принесла.   irfb20n50k.pdf
  • Похожий контент

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