Обучение STM32. Подключаем к STM32 энергонезависимую EEPROM память.

Сайт CAD.Life будет закрыт в начале весны 2018 года. Статьи сохранятся на другом сайте, следите за объявлениями.

В этом уроке по STM32 вы научитесь:

  • подключать EEPROM память
  • конфигурировать шину I2C в CubeMX
  • записывать и считывать байт с EEPROM памяти

 

Для чего нужна EEPROM память

Рис.1. STM32VLDISCOVERY  Итак, рассмотрим подключение к оценочной плате STM32VLDISCOVERY (STM32F100RBT6B) энергонезависимой EEPROM памяти типа 24AA02/24LC02B c двумя килобитами на борту (даташит на микросхему 24AA02-24LC02B.pdf и файлы примера программы прилагаются). EEPROM память позволяет сохранить значения переменных или другие данные при аварийном отключении питания. Обычно блок питания микроконтроллера содержит достаточно энергии (в конденсаторах) для того, чтобы успеть сохранить в память важные данные.

 

Подключение EEPROM памяти

Рис.2. Архитектура шины I2C  В соответствии с описанием эта EEPROM память подключается по шине I2C, используя линии связи SDA (линия данных) и SCL (линия тактирования).

 

Рис.3. Схема подключения EEPROM памяти  Эти линии нужно обязательно подтянуть к напряжению питания резисторами. В нашем случае будем использовать резисторы номиналом 10 кОм (по даташиту 10 кОм используются на частоте тактирования 100 кГц, а 2 кОм используются на частоте тактирования 400 кГц). Вывод WP (Write-Protect Input) подключим к земле для включения режима записи.

 

Рис.4. Адрес EEPROM памяти  Адресные выводы А0-А1-А2 тоже подключим к земле, хотя это и не обязательно, т.к. в этой микросхеме они никуда не подключаются внутри. Они обычно используются для изменения адреса этого устройства в шине I2C (как например, в микросхеме типа 24AA256/24LC256/24FC256). Как видно из даташита, адрес устройства у нас получается из следующих бит (в шине I2C в большинстве случаев используется семибитная адресация): 1010ХХХ – где ХХХ = A0-A1-A2, а нашем случае это 1010000 (0xA0 h), т.к. формально  мы выводы A0-A1-A2 подключили к земле (к нулю). Запомним это число.

 

Конфигурирование шины I2C в CubeMX

Рис.5. Включение шины I2C в CubeMX  Итак, со схемой разобрались – перейдем теперь в CubeMX, где выбираем тип нашего микроконтроллера STM32F100RBT6B. На вкладке Pinout в разделе Peripherals в модуле I2C1 выбираем режим шины I2C. Как видим CubeMX автоматически задействовал порты B6 и B7, согласно даташиту на микроконтроллер. Порт PC8 (подключен к синему светодиоду) настроим на выход для сигнализации о нормальной записи в микросхему.

 

Рис.6. Конфигурирование шины I2C в CubeMX  Перейдем на вкладку Configuration и проверим конфигурацию модуля I2C1. Режим скорости выбран стандартным Standard Mode – максимум 100 кГц, что соответствует номиналу резисторов 10 кОм.

 

 

Рис.7. Скорости шины I2C  CubeMX позволяет выбрать также режим Fast Mode – до 400 кГц. В этом случае нужно было бы применять резисторы 2 кОм. По даташиту, наша микросхема допускает эту частоту работы при напряжении питания выше 2.5 В.

 

Остальные настройки оставим без изменений и сгенерируем код, например для KEIL. В папке Src появился основной файл main.c, вспомогательный файл конфигурирования периферии stm32f1xx_hal_msp.c и файл прерываний stm32f1xx_it.c. Прерывания мы не использовали, поэтому файл stm32f1xx_it.c нас не интересует. В файле stm32f1xx_hal_msp.c можно увидеть инициализацию портов B6 и B7 под шину I2C с помощью функций HAL:

/* stm32f1xx_hal_msp.c */

void HAL_I2C_MspInit(I2C_HandleTypeDef* hi2c)
 
{
  GPIO_InitTypeDef GPIO_InitStruct;
 
  if(hi2c->Instance==I2C1)
 
  {
 
  /* USER CODE BEGIN I2C1_MspInit 0 */
 
  /* USER CODE END I2C1_MspInit 0 */
 
    /**I2C1 GPIO Configuration   
 
    PB6     ------> I2C1_SCL
 
    PB7     ------> I2C1_SDA
 
    */
 
    GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
 
    GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
 
    GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
 
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
 
    /* Peripheral clock enable */
 
    __I2C1_CLK_ENABLE();
 
  /* USER CODE BEGIN I2C1_MspInit 1 */
 
  /* USER CODE END I2C1_MspInit 1 */
 
  }

 

В файле main.c конфигуриуется сама шина I2C, согласно нашим установкам в CubeMX:

/* main.c */

void MX_I2C1_Init(void)
 
{
 
  hi2c1.Instance = I2C1;
 
  hi2c1.Init.ClockSpeed = 100000;
 
  hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
 
  hi2c1.Init.OwnAddress1 = 0;
 
  hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
 
  hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLED;
 
  hi2c1.Init.OwnAddress2 = 0;
 
  hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLED;
 
  hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLED;
 
  HAL_I2C_Init(&hi2c1);
 
}

 

Кстати, иногда CubeMX  глючит и не подключает модуль I2C, и это естественно вызывает кучу ошибок при компиляции. Для ручного включения этого модуля придется открыть файл <Project>\Inc\stm32f1xx_hal_conf.h (это для 100 серии микроконтроллера) и в нем раскомментировать строку #define HAL_I2C_MODULE_ENABLED.

 

Запись и чтение байта с EEPROM памяти

Инициализация других объектов нас сейчас мало интересует, поэтому переходим сразу к нашей задаче. В разделе пользовательских переменных инициализируем нужные нам объекты: однобайтный буфер обмена xBuffer, адрес EEPROM микросхемы I2C1_DEVICE_ADDRESS = 0x50 по шине I2C и ячейку памяти MEMORY_ADDRESS с адресом 0x08.

/* main.c */

/* USER CODE BEGIN PV */
 
/* Private variables ---------------------------------------------------------*/
 
uint8_t xBuffer[1];
 
#define I2C1_DEVICE_ADDRESS      0x50   /* A0 = A1 = A2 = 0 */
 
#define MEMORY_ADDRESS                                                    0x08
 
 
/* USER CODE END PV */

 

Важно помнить, что запись своего кода всегда нужно осуществлять в строго отведенных блоках.

/* main.c */

 /* USER CODE BEGIN…*/
 
/* USER CODE END…*/

 

Тем самым, при следующей генерации кода CubeMX не сотрет эти блоки. Теперь перейдем к записи и чтению одного байта из памяти. В бесконечном цикле main запишем:

/* main.c */

  /* USER CODE BEGIN WHILE */
 
  while (1)
 
  {
 
  /* USER CODE END WHILE */
 
                xBuffer[0] = 'M'; //0x4D
 
                HAL_I2C_Mem_Write(&hi2c1, (uint16_t) I2C1_DEVICE_ADDRESS<<1, MEMORY_ADDRESS, 1, xBuffer, 1, 5);                //write to memory address 08
 
                osDelay(10); //memory write delay
 
                xBuffer[0] = 0x00; //clear buffer
 
                osDelay(5000); //system wait
 
                HAL_I2C_Mem_Read(&hi2c1, (uint16_t) I2C1_DEVICE_ADDRESS<<1, MEMORY_ADDRESS, 1, xBuffer, 1, 5); //read memory address 08
 
                if (xBuffer[0] == 0x4D) // if xBuffer[0] = 'M'
 
                {
                               HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8, GPIO_PIN_SET); //BLUE LED ON
                }
 
  /* USER CODE BEGIN 3 */
 
  }

 

Результатом работы будет включение синего светодиода через 5 секунд после запуска. Обнулить ячейку памяти возможно с помощью другой программы, с этим должно быть все ясно.

Функции HAL_I2C_Mem_Write и HAL_I2C_Mem_Read предназначены для работы с памятью, и поэтому нет необходимости использовать общие функции работы с шиной I2C HAL_I2C_Master_Transmit и HAL_I2C_ Master_Receive, тем более, что с ними есть некоторые проблемы при работе с памятью. Адрес EEPROM необходимо передавать в смещенном виде I2C1_DEVICE_ADDRESS<<1, т.к. он является семибитным, а последний восьмой  бит по спецификации I2C является флагом записи/чтения. HAL его настраивает самостоятельно.

В функциях HAL_I2C_Mem_Write и HAL_I2C_Mem_Read последним аргументом является время ожидания, за которое микроконтроллер стабильно будет получать реакцию от EEPROM на его запросы. Задержка в 10 мс osDelay(10) после функции HAL_I2C_Mem_Write необходима на совершение операции записи в EEPROM, хотя в даташите на EEPROM указано время записи 5 мс (Write cycle time (byte or page)). Но лучше перестраховаться, если не нужна большая скорость записи.

Другие аргументы функций HAL_I2C_Mem_Write и HAL_I2C_Mem_Read думается, не вызовут больших вопросов. Код проекта доступен по кнопке Скачать пример для всех пользователей.

Поделиться ссылкой на статью

CADLife - лучший инженерный опыт, бесплатные уроки и обучение Solidworks, Creo, Pro/Engineer, STM32

Комментарии к статье

1 год назад
пишет ошибку при компиляции -
Error: L6218E: Undefined symbol HAL_I2C_Init (referred from main.o).

Администратор

1 год назад
Все просто - компилятор не может найти ссылку на функцию HAL_I2C_Init ...

Значит не включен модуль HAL_I2C в \Inc\stm32f1xx_hal_conf.h (строку #define HAL_I2C_MODULE_ENABLED - раскомментировать). Или не добавлен файл, к примеру, \Drivers\STM32F1xx_HAL_Driver\Src\stm32f1xx_hal_i2c.c в дерево проекта...
спасибо, уже нашел)) действительно файл не был добавлен в дерево
это еще цветочки....чуть не сдался уже от нестабильности работы памяти....вылечил только заменой подтягивающих резисторов на 4,7кОм

Администратор

1 год назад
Может еще линия слишком длинная получилась? У меня было небольшое расстояние между памятью и платой микроконтроллера.
ваш способ не пишет данные в eeprom свыше 256 байт!!!!!!!!!!

Администратор

1 год назад
1 год назад отредактировал Администратор Цитата
Свыше числа 256 начинается 16 битная адресация. Функцию HAL_I2C_Mem_Write надо бы по идее записывать так: HAL_I2C_Mem_Write(&hi2c1, (uint16_t) I2C1_WRITE_ADDRESS<<1, ALARM_ADDRESS_S, 10, xBuffer, 1, 5);

Поясню. Для восьмибитной адресации HAL_I2C_Mem_Write(&hi2c1, (uint16_t) I2C1_WRITE_ADDRESS<<1, ALARM_ADDRESS_S, I2C_MEMADD_SIZE_8BIT , xBuffer, 1, 5);//где I2C_MEMADD_SIZE_8BIT = 1 - см. \Drivers\STM32F1xx_HAL_Driver\Inc\stm32f1xx_hal_i2c.h
Для шестнадцатибитной адресации HAL_I2C_Mem_Write(&hi2c1, (uint16_t) I2C1_WRITE_ADDRESS<<1, ALARM_ADDRESS_S, I2C_MEMADD_SIZE_16BIT , xBuffer, 1, 5); //где I2C_MEMADD_SIZE_16BIT = 10

Но я это не проверял. Лучше бы еще посмотреть даташит на эту память - может там еще что-то есть особенное.
9 месяцев назад
I2C1_DEVICE_ADDRESS<<1
а сразу 0хА0 нельзя ?

Администратор

9 месяцев назад
Конечно можно!!!. Это же из языка СИ понятно.Тут обучающий сайт, поэтому разложено по полочкам.
8 месяцев назад
I2C1_DEVICE_ADDRESS<<1
а сразу 0хА0 нельзя ?
Никогда не заменяйте определения (да еще с операторами) на значения. Поменяется адрес устройства на шине, в собственном коде запутаетесь.
7 месяцев назад
thank bro!!!
5 месяцев назад
Может кто-нибудь поможет мне,
Я получаю ошибку на HAL_I2C_Mem_Write() функции -
if(hi2c->ErrorCode == HAL_I2C_ERROR_AF)
{
}
Что это за ошибка?
(Read функция работает хорошо)

Спасибо

Администратор

5 месяцев назад
По любому адресу записи это происходит?

Администратор

5 месяцев назад
Вывод WP (write protection) не забыли к земле притянуть?

Написать ответ...