В общем понадобилось тут подвесить 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; // вернуть считанное значение
}