[Программирование микроконтроллеров] STM32 и бесконтактный датчик температуры MLX90614. Подключение по I2C

Автор Сообщение
news_bot ®

Стаж: 6 лет 9 месяцев
Сообщений: 27286

Создавать темы news_bot ® написал(а)
18-Ноя-2020 23:31

Датчик MLX90614 - это датчик с бесконтактным считыванием температуры объекта посредством приема и преобразования инфракрасного излучения. Он умеет работать в трех режимах: термостат, ШИМ выход и SMBus. В режиме термостат датчику не требуется контроллер, он просто держит температуру в заданных пределах, управляя драйвером нагрузки открытым стоком. В режиме ШИМ на выходе датчика появляется сигнал ШИМ, скважность которого зависит от температуры. В целях подключения к контроллеру наиболее интересен режим SMBus. Так как этот протокол электрически и сигнально совместим с I2C мы будем работать с датчиком, используя аппаратный I2C. О нем и пойдет речь в данной статье. Все режимы датчика настраиваются записью в определенные ячейки EEPROM. По умолчанию датчик находится в режиме SMBus. Внешний вид и схема подключения
В подключении датчика нет ничего сложного. У меня есть плата "Синяя таблетка" с контроллером STM32F103C8T6 на борту, вот к ней и будем подключать датчик. У этого контроллера 2 аппаратных интерфейса I2C. Для датчика будет использоваться первый на выводах по умолчанию. Это PB6 - SCL, PB7 - SDA. При подключении необходимо не забыть подтянуть эти выводы к питанию внешними резисторами, у меня их сопротивление 4.7 кОм. Программная частьВесь код я решил оформить в виде библиотеки, состоящей из двух файлов: mlx90614.h и mlx90614.c . Также в проекте используется библиотека системного таймера для задержек и библиотека LCD дисплея A2004 для вывода температуры. Они описаны в прошлой статье.Начнем с заголовочного файла.
#ifndef I2C_DEVICES_I2C_H_
#define I2C_DEVICES_I2C_H_
#include "stm32f1xx.h"
#include <stdio.h>
#include "delay.h"
#define F_APB1 36                           // частота шины APB1
#define TPCLK1 ( 1000/F_APB1 )               // период частоты APB1 ns. ~ 28
#define CCR_VALUE ( 10000 /(TPCLK1 * 2 ) )   // значение регистра CCR Для 36 Мгц ~ 179
#define TRISE_VALUE ( 1000 / TPCLK1)
В начале подключаем заголовочный файл для своего контроллера. У меня это stm32f1xx.h. Стандартная библиотека СИ stdio.h нужна для того, чтобы переводить дробные числа в char массив для вывода на LCD. delay.h - библиотека для организации задержек. Далее идут константы для инициализации аппаратного I2C. В последствии в коде они подставятся в нужные регистры. Это сделано для того, чтобы меняя частоту тактирования изменить только макроопределение F_APB1, а не копаться в коде и исправлять на новое значение.Далее идем в даташит и узнаем, что датчик имеет две разные памяти: RAM и EEPROM. RAM используется для считывания температуры. Внутренний процессор датчика считывает температуру с сенсоров, обрабатывает и кладет в ячейки RAM температуру в Кельвинах. В первых двух ячейках хранится "сырая температура". Я не понял что она из себя представляет. В следующих ячейках температура кристалла датчика, температура первого и второго сенсора. Датчики MLX90614 бывают с одним и двумя датчиками. У меня с одним, поэтому температура будет читаться с первого сенсора. В EEPROM конфигурируется работа датчика. Для удобства запишем адресацию памяти в заголовочном файле в виде макроопределений.
//---------------- RAM addresses --------------------------------------------
#define MLX90614_RAW_IR_1 0x04    // сырые данные с сенсоров
#define MLX90614_RAW_IR_2 0x05
#define MLX90614_TA 0x06          // температура кристалла датчика
#define MLX90614_TOBJ_1 0x07      // температура с первого ИК сенсора
#define MLX90614_TOBJ_2 0x08      // температура со второго ИК сенсора
//--------------- EEPROM addresses ------------------------------------------
#define MLX90614_TO_MAX 0x00
#define MLX90614_TO_MIN 0x01
#define MLX90614_PWM_CTRL 0x02
#define MLX90614_TA_RANGE 0x03
#define MLX90614_EMISSIVITY 0x04
#define MLX90614_CONFIG_REGISTER_1 0x05
#define MLX90614_SMBUS_ADDRESS 0x0E // LSByte only
#define MLX90614_ID_NUMBER_1 0x1C
#define MLX90614_ID_NUMBER_2 0x1D
#define MLX90614_ID_NUMBER_3 0x1E
#define MLX90614_ID_NUMBER_4 0x1F
Также из документации к датчику переносим команды, с которыми он умеет работать.
//--------------- Commands ------------------------------------------------
#define MLX90614_RAM_ACCESS 0           // доступ к RAM
#define MLX90614_EEPROM_ACCESS 0x20     // доступ к EEPROM
#define MLX90614_READ_FLAGS 0xF0         // чтение флагов
#define MLX90614_ENTER_SLEEP_MODE 0xFF   // режим сна
#define MLX90614_READ 1                  // режим чтения из датчика
#define MLX90614_WRITE 0                // режим записи в датчик
С макроопределениями закончили. Теперь нужно определить функции, с помощью которых контроллер будет взаимодействовать с датчиком. Чтобы комфортно работать с датчиком нужно уметь считывать температуру и считывать и изменять адрес устройства, так как на шине I2C может быть несколько таких датчиков, а адрес по умолчанию у всех одинаковый - 5A. Я задумывал в своем устройстве использовать два таких датчика, но почитав форумы понял, что они для моих целей не подходят. Так как датчики уже были у меня я решил написать под них библиотеку на будущее.Итак определяем функции:
void mlx90614Init( void );
double getTemp_Mlx90614_Double( uint16_t address, uint8_t ram_address );
void getTemp_Mlx90614_CharArray( uint16_t address, uint8_t ram_address, char* buf );
uint16_t getAddrFromEEPROM( uint16_t address );
int setAddrToEEPROM ( uint16_t address, uint16_t new_address );
uint16_t readEEPROM( uint16_t address, uint16_t eeprom_address );
void writeEEPROM ( uint16_t address, uint16_t eeprom_address, uint16_t data );
#endif /* I2C_DEVICES_I2C_H_ */
void mlx90614Init( void )Инициализация I2C для работы с датчикомdouble getTempMlx90614Double( uint16t address, uint8t ram_address )Возвращает температуру в формате double приведенную к градусам Цельсия. Применяется, если нужна дальнейшая обработка численного значения. Принимает адрес датчика и адрес RAM памяти из которого читать данные. В зависимости от адреса RAM вернет температуру кристалла, сенсора 1 или 2.void getTempMlx90614CharArray( uint16t address, uint8t ram_address, char* buf )Аналогичная предыдущей функции, за исключение того, что возвращает значение в char массив, переданный по ссылке. Удобно применять для вывода температуры на LCDuint16t getAddrFromEEPROM( uint16t address )Возвращает адрес датчика, записанный в его EEPROM. Принимает текущий адрес датчика. int setAddrToEEPROM ( uint16t address, uint16t new_address )Записывает адрес датчика в EEPROM. Применяется для изменения адреса датчика.uint16t readEEPROM( uint16t address, uint16t eepromaddress )Универсальная функция чтения EEPROMvoid writeEEPROM ( uint16t address, uint16t eepromaddress, uint16t data )Универсальная функция записи в EEPROMС заголовочным файлом закончили. Самое время написать реализации функций.
void mlx90614Init(void){
  delay_ms(120);                                // на стабилизацию питания и переходные процессы в датчике
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;              // тактируем порт
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;              // тактируем i2c1
GPIOB->CRL |= GPIO_CRL_MODE6 | GPIO_CRL_MODE7;  // выход 50 мгц
GPIOB->CRL |= GPIO_CRL_CNF6 | GPIO_CRL_CNF7;     // альтернативная ф-я открытый сток
I2C1->CR2 &= ~I2C_CR2_FREQ;                     // скидываем биты частоты шины тактирования  APB1
I2C1->CR2 |= F_APB1;                             // устанавливаем частоту шины APB1 от которой тактируется I2C модуль
I2C1->CR1 &= ~I2C_CR1_PE;                       // выключаем модуль I2C для настройки регистра CCR
I2C1->CCR &= ~I2C_CCR_CCR;
I2C1->CCR |=  CCR_VALUE;
I2C1->TRISE |=  TRISE_VALUE;
I2C1->CR1 |= I2C_CR1_ENPEC;                     // разрешаем отсылку PEC
I2C1->CR1 |= I2C_CR1_PE;                        // включаем модуль I2C
I2C1->CR1 |= I2C_CR1_ACK;                       // разрешаем ACK
}
Из комментариев в функции понятно что в ней делается. Единственное следует обратить внимание на F_APB1, CCR_VALUE и TRISE_VALUE они берутся из заголовочного файла, там и рассчитываются исходя из заданной частоты тактирования. Так же важно отключить модуль I2C перед настройкой регистра CCR ( это указано в документации на контроллер ) и разрешить ACK после запуска модуля I2C , иначе ACK работать не будет.
double getTemp_Mlx90614_Double( uint16_t address,
                uint8_t ram_address){
  uint16_t  temp ;                                 // температура
  uint8_t  temp_lsb ;                            // младшие биты температуры
  double temp_result ;                           // результирующая пересчитанная температура
  double temp_double ;                            // температура, приведенная к формату double
  address = address<<1;                           // сдвигаем на один влево (так датчик принимает
                                                  // адрес плюс 1-й бит запись/чтение)
  I2C1->CR1 |= I2C_CR1_START;
  while (!(I2C1->SR1 & I2C_SR1_SB)){}
  (void) I2C1->SR1;
  I2C1->DR = address | MLX90614_WRITE;           // режим записи , передача адреса MLX90614
  while (!(I2C1->SR1 & I2C_SR1_ADDR)){}
  (void) I2C1->SR1;
  (void) I2C1->SR2;
  I2C1->DR= ram_address;                          // передача адреса RAM  датчика MLX90614
  while (!(I2C1->SR1 & I2C_SR1_TXE)){}
  I2C1->CR1 |= I2C_CR1_START;                    // повторный старт
  while (!(I2C1->SR1 & I2C_SR1_SB)){}
  (void) I2C1->SR1;
  I2C1->DR = address | MLX90614_READ;            // обращение к датчику для чтения
  while (!(I2C1->SR1 & I2C_SR1_ADDR)){}
  (void) I2C1->SR1;
  (void) I2C1->SR2;
  while(!(I2C1->SR1 & I2C_SR1_RXNE)){}
  temp_lsb = I2C1->DR;                            // читаем младший байт
  while(!(I2C1->SR1 & I2C_SR1_RXNE)){}
  temp = I2C1->DR;                                // читаем старший байт
  I2C1->CR1 |= I2C_CR1_STOP;
  temp = (temp & 0x007F) << 8;                    // удаляем бит ошибки, сдвигаем в старший байт
  temp |= temp_lsb;
  temp_double = (double) temp;                   // приводим к формату double
  temp_result =  ((temp_double * 0.02)- 0.01 );  // умножаем на разрешение измерений
  temp_result = temp_result - 273.15;             // и приводим к град. Цельсия
return temp_result;
}
Здесь следует обратить внимание, что адрес датчика сдвигается на 1 бит влево, а потом в первый бит адреса записывается 1 для чтения, 0 - для записи. В документации на датчик не очень очевидно освещен процесс передачи адреса по I2C. И там не понятно почему обращаемся по адресу 5A, а на временной диаграмме B4 для записи и B5 для чтения. Принимая во внимание тот факт, что мы сдвигаем адрес влево и прибавляем бит режима доступа, все встает на свои места. Еще есть одна тонкость. В старшем бите старшего байта передается бит ошибки. Его необходимо удалить перед дальнейшей обработкой, что мы и делаем перед сдвигом в старший байт - (temp & 0x007F).Получить значение температуры конечно хорошо, но еще лучше вывести это значение на LCD, например. Для этого есть простенькая функция void getTempMlx90614CharArray, которая просто преобразует полученное значение из предыдущей функции в char массив, используя для этого функцию стандартной библиотеки СИ sprintf(), которая объявлена в файле stdio.h
void getTemp_Mlx90614_CharArray( uint16_t address, uint8_t ram_address, char* buf){
  double t;
  t = getTemp_Mlx90614_Double(address,ram_address);
  sprintf(buf, "%.1f",t);
  return ;
}
Общий алгоритм чтения из RAM датчика выглядит так:
  • START
  • Передаем адрес датчика, сдвинутый на 1 бит влево плюс бит записи (первый). Для записи - 0
  • Передаем адрес RAM откуда читать плюс команда доступа к RAM. Адресом RAM может быть температура кристалла датчика или температура одного из двух инфракрасных сенсоров.
  • Повторный START
  • Передаем адрес датчика сдвинутый на 1 влево плюс бит чтения.
  • Читаем младший байт
  • Читаем старший байт
  • STOP
Теперь мы умеем читать температуру из датчика. Осталось реализовать возможность менять адрес, чтобы можно было вешать несколько датчиков на шину. Но перед этим напишем две вспомогательные функции для работы с EEPROM для записи и чтения.Алгоритм чтения из EEPROM выглядит следующим образом:
  • START
  • Передаем адрес датчика, сдвинутый на 1 бит влево плюс бит записи (первый). Для записи - 0
  • Передаем адрес EEPROM откуда читать плюс команда доступа к EEPROM ( определена в заголовочном файле )
  • Повторный START
  • Передаем адрес датчика плюс бит чтения
  • Читаем младший байт
  • Читаем старший байт
  • STOP
Функция чтения из EEPROM
uint16_t readEEPROM( uint16_t address, uint16_t eeprom_address ){
  uint16_t  data_msb;
  uint16_t  data_lsb;
  uint16_t data_result;
  address = address<<1;                                 // сдвигаем на один влево (так датчик принимает адрес + 1 бит чтение/запись)
    I2C1->CR1 |= I2C_CR1_START;
    while (!(I2C1->SR1 & I2C_SR1_SB)){}
    (void) I2C1->SR1;
    I2C1->DR = address | MLX90614_WRITE;               // режим записи , передача адреса MLX90614
    while (!(I2C1->SR1 & I2C_SR1_ADDR)){}
    (void) I2C1->SR1;
    (void) I2C1->SR2;
    I2C1->DR= eeprom_address | MLX90614_EEPROM_ACCESS;  // передача адреса EEPROM датчика MLX90614
    while (!(I2C1->SR1 & I2C_SR1_TXE)){}
    I2C1->CR1 |= I2C_CR1_START;                        // повторный старт
    while (!(I2C1->SR1 & I2C_SR1_SB)){}
    (void) I2C1->SR1;
    I2C1->DR = address | MLX90614_READ;                // обращение к датчику для чтения
    while (!(I2C1->SR1 & I2C_SR1_ADDR)){}
    (void) I2C1->SR1;
    (void) I2C1->SR2;
    //I2C1->CR1 &= ~I2C_CR1_ACK;
    while (!(I2C1->SR1 & I2C_SR1_RXNE)){};
    data_lsb = I2C1->DR;                              // читаем младший байт
    while(!(I2C1->SR1 & I2C_SR1_RXNE)){}
    data_msb = I2C1->DR;                              // читаем старший байт
    I2C1->CR1 |= I2C_CR1_STOP;
    data_result = ((data_msb << 8) | data_lsb) ;
    return data_result;
}
Чтение из EEPROM осуществляется по аналогичному алгоритму чтения из RAM. Разница только в командах выбора памяти и адресах этой памяти.С записью немного иначе. Алгоритм следующий:
  • START
  • Передаем адрес датчика, сдвинутый влево плюс бит записи
  • Передаем адрес EEPROM плюс команда выбора EEPROM памяти
  • Передаем младший байт
  • Передаем старший байт
  • Передаем PEC (байт контрольной суммы )
  • STOP
Здесь повторный старт не используется, а сразу пишется два байта адреса. Обратите внимание, что адрес использует только младший байт, поэтому в старший пишутся нули.Функция записи в EEPROM
void writeEEPROM ( uint16_t address, uint16_t eeprom_address, uint16_t data ){
    address = address<<1;                                 // сдвигаем на один влево (т.к. датчик принимает адрес + 1 бит чтение/запись)
      I2C1->CR1 |= I2C_CR1_START;
      while (!(I2C1->SR1 & I2C_SR1_SB)){}
      (void) I2C1->SR1;
      I2C1->DR = address | MLX90614_WRITE;               // режим записи , передача адреса MLX90614
      while (!(I2C1->SR1 & I2C_SR1_ADDR)){}
      (void) I2C1->SR1;
      (void) I2C1->SR2;
      I2C1->DR= eeprom_address | MLX90614_EEPROM_ACCESS;  // передача адреса EEPROM датчика MLX90614
      while (!(I2C1->SR1 & I2C_SR1_TXE)){}
      I2C1->DR =  ( uint8_t ) ( data & 0x00FF );          // пишем младший байт
      while(!(I2C1->SR1 & I2C_SR1_BTF)){}
      I2C1->DR = ( uint8_t ) ( data >> 8 )  ;              // пишем старший байт
      while(!(I2C1->SR1 & I2C_SR1_BTF)){}
      I2C1->CR1 |= I2C_CR1_PEC;                          // посылаем PEC
      I2C1->CR1 |= I2C_CR1_STOP;
      return ;
}
Теперь у нас есть две функции для работы с EEPROM. На них основана работа двух следующих функций для чтения и записи адреса датчика. Но они так же могут использоваться для расширения функционала библиотеки, например для изменения режима работы или перехода датчика в спящий режим.Функция чтения адреса датчика
uint16_t getAddrFromEEPROM ( uint16_t address ){
  uint16_t addr_eeprom;
  addr_eeprom = readEEPROM( address, MLX90614_SMBUS_ADDRESS );
  return addr_eeprom;
}
Тут все просто. Функция принимает текущий адрес датчика, читает с помощью readEEPROM() текущий адрес из EEPROM и возвращает его.С записью нового адреса в EEPROM немного сложнее. Даташит на MLX90614 рекомендует следующий алгоритм записи в EEPROM:
  • Включение питания
  • Запись в ячейку нулей, тем самым эффективно стирая ее
  • Ждем 10 миллисекунд
  • Пишем новое значение ячейки
  • Ждем еще 10 миллисекунд
  • Читаем ячейку для сравнения с исходным значением, чтобы убедиться, что записано без ошибок
  • Выключаем питание
От себя дополню. Новый адрес датчика будет использоваться только после выключения и повторного включения питания датчика. В связи с этим необходимо предусмотреть отдельный пин контроллера, который будет управлять включением и выключением датчика через, например, транзистор. Здесь возникает вопрос - зачем вообще нужен режим сна, если все равно для работы с EEPROM необходимо управлять питанием датчика? Не проще ли тогда просто отключать питание? Поэтому я не стал реализовывать режим сна, так как в таких обстоятельствах он не имеет смысла. Так же в этой статье я не рассматриваю управление питанием, так как реализовать его не сложно в реальном проекте.Функция записи в EEPROM
int setAddrToEEPROM ( uint16_t address, uint16_t new_address ){
  uint16_t addr;
  writeEEPROM ( address, MLX90614_SMBUS_ADDRESS, 0x0);           // стираем ячейку
  delay_ms(10);
  writeEEPROM (address, MLX90614_SMBUS_ADDRESS, new_address );   // пишем новый адрес
  delay_ms(10);
  addr = readEEPROM ( address, MLX90614_SMBUS_ADDRESS );        // читаем для сравнения
  if ( addr == new_address){
    return 1;
  }
  else return 0;
}
И наконец пришло время опробовать работу библиотеки. Для этого пишем небольшой скетч в main() функции проекта
#include "main.h"
#include <stdio.h>
int main (void){
  clk_ini();                     // запускаем тактирование переферии
  lcd_2004a_init();             // инициализация дисплея a2004
  mlx90614Init();               // инициализация I2C для датчика
  uint16_t geted_eeprom_address;
  char char_eeprom_address[20];
  char crystal_temp[10];         // массив для строки температуры
  char first_sensor_temp[10];
// читаем адрес датчика из EEPROM и выводим на LCD
  geted_eeprom_address = getAddrFromEEPROM( 0x5A );
  sprintf(char_eeprom_address, "%x", (uint8_t) geted_eeprom_address);
  sendStr("addr value:", 3, 0);
  sendStr (char_eeprom_address, 3, 14 );
  setAddrToEEPROM (0x5A , 0xA); // записываем новый адрес
// снова читаем адрес и выводим на LCD
  geted_eeprom_address = getAddrFromEEPROM( 0x5A );
  sprintf(char_eeprom_address, "%x", (uint8_t) geted_eeprom_address);
  sendStr("new addr :", 4, 0);
  sendStr (char_eeprom_address, 4, 14 );
  while(1){
// читаем и выводим температуру кристалла и сенсора датчика
    getTemp_Mlx90614_CharArray ( 0x5A,  MLX90614_TA, crystal_temp );
    sendStr( "Crystal Temp :", 1, 0 );
    sendStr( crystal_temp, 1, 14 );
    delay_s(1);
    getTemp_Mlx90614_CharArray ( 0x5A,  MLX90614_TOBJ_1, first_sensor_temp );
    sendStr( "Sensor Temp  :", 2, 0 );
    sendStr( first_sensor_temp, 2, 14 );
      delay_s(1);
  }
}
В main.h подключаем
#ifndef CORE_INC_MAIN_H_
#define CORE_INC_MAIN_H_
#include "stm32f1xx.h"
#include "clk_ini.h" // тактирование контроллера
#include "delay.h"    // функции задержки
#include "lcd_20x4.h" // функции для работы с LCD A2004
#include "mlx90614.h" // функции работы с датчиком
#endif /* CORE_INC_MAIN_H_ */
У меня получилось вот так
В заключение полный листинги проектаmlx90614.h
#ifndef I2C_DEVICES_I2C_H_
#define I2C_DEVICES_I2C_H_
#include "stm32f1xx.h"
#include <stdio.h>
#include "delay.h"
#define F_APB1 36 // частота шины APB1
#define TPCLK1 ( 1000/F_APB1 ) // период частоты APB1 ns. ~ 28
#define CCR_VALUE ( 10000 /(TPCLK1 * 2 ) ) // значение регистра CCR Для 36 Мгц ~ 179
#define TRISE_VALUE ( 1000 / TPCLK1)
//---------------- RAM addresses --------------------------------------------
#define MLX90614_RAW_IR_1 0x04    // сырые данные с сенсоров
#define MLX90614_RAW_IR_2 0x05
#define MLX90614_TA 0x06      // температура кристалла датчика
#define MLX90614_TOBJ_1 0x07    // температура с первого ИК сенсора
#define MLX90614_TOBJ_2 0x08    // температура со второго ИК сенсора
//--------------- EEPROM addresses ------------------------------------------
#define MLX90614_TO_MAX 0x00
#define MLX90614_TO_MIN 0x01
#define MLX90614_PWM_CTRL 0x02
#define MLX90614_TA_RANGE 0x03
#define MLX90614_EMISSIVITY 0x04
#define MLX90614_CONFIG_REGISTER_1 0x05
#define MLX90614_SMBUS_ADDRESS 0x0E // LSByte only
#define MLX90614_ID_NUMBER_1 0x1C
#define MLX90614_ID_NUMBER_2 0x1D
#define MLX90614_ID_NUMBER_3 0x1E
#define MLX90614_ID_NUMBER_4 0x1F
//--------------- Commands ------------------------------------------------
#define MLX90614_RAM_ACCESS 0       // доступ к RAM
#define MLX90614_EEPROM_ACCESS 0x20   // доступ к EEPROM
#define MLX90614_READ_FLAGS 0xF0     // чтение флагов
#define MLX90614_ENTER_SLEEP_MODE 0xFF   // режим сна
#define MLX90614_READ 1          // режим чтения из датчика
#define MLX90614_WRITE 0        // режим записи в датчик
void mlx90614Init( void );
double getTemp_Mlx90614_Double( uint16_t address, uint8_t ram_address );
void getTemp_Mlx90614_CharArray( uint16_t address, uint8_t ram_address, char* buf );
uint16_t getAddrFromEEPROM( uint16_t address );
int setAddrToEEPROM ( uint16_t address, uint16_t new_address );
uint16_t readEEPROM( uint16_t address, uint16_t eeprom_address );
void writeEEPROM ( uint16_t address, uint16_t eeprom_address, uint16_t data );
#endif /* I2C_DEVICES_I2C_H_ */
mlx90614.c
#include "mlx90614.h"
/********************************************************************************************
* Функция инициализирует I2C интерфейс для работы с датчиком MLX90614             *
*                                               *
********************************************************************************************/
void mlx90614Init(void){
  delay_ms(120);                // на стабилизацию питания и переходные процессы в датчике
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;        // тактируем порт
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;        // тактируем i2c1
GPIOB->CRL |= GPIO_CRL_MODE6 | GPIO_CRL_MODE7;  // выход 50 мгц
GPIOB->CRL |= GPIO_CRL_CNF6 | GPIO_CRL_CNF7;   // альтернативная ф-я открытый сток
I2C1->CR2 &= ~I2C_CR2_FREQ;           // скидываем биты частоты шины тактирования  APB1
I2C1->CR2 |= F_APB1;               // устанавливаем частоту шины APB1 от которой тактируется I2C модуль
I2C1->CR1 &= ~I2C_CR1_PE;             // выключаем модуль I2C для настройки регистра CCR
I2C1->CCR &= ~I2C_CCR_CCR;
I2C1->CCR |=  CCR_VALUE;
I2C1->TRISE |=  TRISE_VALUE;
I2C1->CR1 |= I2C_CR1_ENPEC;           // разрешаем отсылку PEC
I2C1->CR1 |= I2C_CR1_PE;            // включаем модуль I2C
I2C1->CR1 |= I2C_CR1_ACK;             // разрешаем ACK
}
/********************************************************************************************
* Функция возвращает значение температуры в град. Цельсия и типом double.           *
*                                                 *
* Входные данные:                                          *
* address -         адрес датчика MLX90614                         *
*                                                  *
* ram_address RAM-адрес для чтения ( см. константы в .h файле ) :                  *
*                                                  *
*   MLX90614_TA -     температура кристалла датчика                       *
*   MLX90614_TOBJ_1 -   температура первого ИК сенсора                       *
*   MLX90614_TOBJ_2 -   температура второго ИК сенсора                      *
  *******************************************************************************************/
double getTemp_Mlx90614_Double( uint16_t address,
                uint8_t ram_address){
  uint16_t  temp ;             // температура
  uint8_t  temp_lsb ;          // младшие биты температуры
  double temp_result ;           // результирующая пересчитанная температура
  double temp_double ;          // температура, приведенная к формату double
  address = address<<1;             // сдвигаем на один влево (так датчик принимает
                        // адрес плюс 1-й бит запись/чтение)
  I2C1->CR1 |= I2C_CR1_START;
  while (!(I2C1->SR1 & I2C_SR1_SB)){}
  (void) I2C1->SR1;
  I2C1->DR = address | MLX90614_WRITE;     // режим записи , передача адреса MLX90614
  while (!(I2C1->SR1 & I2C_SR1_ADDR)){}
  (void) I2C1->SR1;
  (void) I2C1->SR2;
  I2C1->DR= ram_address;            // передача адреса RAM  датчика MLX90614
  while (!(I2C1->SR1 & I2C_SR1_TXE)){}
  I2C1->CR1 |= I2C_CR1_START;        // повторный старт
  while (!(I2C1->SR1 & I2C_SR1_SB)){}
  (void) I2C1->SR1;
  I2C1->DR = address | MLX90614_READ;      // обращение к датчику для чтения
  while (!(I2C1->SR1 & I2C_SR1_ADDR)){}
  (void) I2C1->SR1;
  (void) I2C1->SR2;
  while(!(I2C1->SR1 & I2C_SR1_RXNE)){}
  temp_lsb = I2C1->DR;              // читаем младший байт
  while(!(I2C1->SR1 & I2C_SR1_RXNE)){}
  temp = I2C1->DR;                // читаем старший байт
  I2C1->CR1 |= I2C_CR1_STOP;
  temp = (temp & 0x007F) << 8;          // удаляем бит ошибки, сдвигаем в старший байт
  temp |= temp_lsb;
  temp_double = (double) temp;           // приводим к формату double
  temp_result =  ((temp_double * 0.02)- 0.01 );  // умножаем на разрешение измерений
  temp_result = temp_result - 273.15;       // и приводим к град. Цельсия
return temp_result;
}
/********************************************************************************************
* Функция записывает в, переданный по ссылке, массив типа char температуру в град. Цельсия *
*                                               *
* Входные данные:                                       *
* address -       адрес датчика MLX90614                        *
*                                               *
* ram_address RAM-адрес для чтения ( см. константы в .h файле ) :               *
*                                               *
*   MLX90614_TA -     температура кристалла датчика                    *
*   MLX90614_TOBJ_1 -   температура первого ИК сенсора                    *
*   MLX90614_TOBJ_2 -   температура второго ИК сенсора                    *
*                                               *
*   *buf -         ссылка на массив                          *
*******************************************************************************************/
void getTemp_Mlx90614_CharArray( uint16_t address, uint8_t ram_address, char* buf){
  double t;
  t = getTemp_Mlx90614_Double(address,ram_address);
  sprintf(buf, "%.1f",t);
  return ;
}
/********************************************************************************************
* Чтение  EEPROM датчика по произвольному адресу                      *
* Входные данные:                                      *
* address - адрес датчика                                  *
* eeprom_address - адрес в EEPROM                              *
*                                               *
* Выходные данные:                                      *
* значение в ячейке EEPROM формат uint16_t                          *
*                                              *
* ******************************************************************************************/
uint16_t readEEPROM( uint16_t address, uint16_t eeprom_address ){
  uint16_t  data_msb;
  uint16_t  data_lsb;
  uint16_t data_result;
  address = address<<1;                   // сдвигаем на один влево (так датчик принимает адрес + 1 бит чтение/запись)
    I2C1->CR1 |= I2C_CR1_START;
    while (!(I2C1->SR1 & I2C_SR1_SB)){}
    (void) I2C1->SR1;
    I2C1->DR = address | MLX90614_WRITE;         // режим записи , передача адреса MLX90614
    while (!(I2C1->SR1 & I2C_SR1_ADDR)){}
    (void) I2C1->SR1;
    (void) I2C1->SR2;
    I2C1->DR= eeprom_address | MLX90614_EEPROM_ACCESS;  // передача адреса EEPROM датчика MLX90614
    while (!(I2C1->SR1 & I2C_SR1_TXE)){}
    I2C1->CR1 |= I2C_CR1_START;            // повторный старт
    while (!(I2C1->SR1 & I2C_SR1_SB)){}
    (void) I2C1->SR1;
    I2C1->DR = address | MLX90614_READ;        // обращение к датчику для чтения
    while (!(I2C1->SR1 & I2C_SR1_ADDR)){}
    (void) I2C1->SR1;
    (void) I2C1->SR2;
    //I2C1->CR1 &= ~I2C_CR1_ACK;
    while (!(I2C1->SR1 & I2C_SR1_RXNE)){};
    data_lsb = I2C1->DR;                // читаем младший байт
    while(!(I2C1->SR1 & I2C_SR1_RXNE)){}
    data_msb = I2C1->DR;                  // читаем старший байт
    I2C1->CR1 |= I2C_CR1_STOP;
    data_result = ((data_msb << 8) | data_lsb) ;//& 0x1F;
    return data_result;
}
/********************************************************************************************
* Запись в EEPROM по произвольному адресу                             *
*                                                 *
* Входные данные:                                        *
* address -     адрес датчика                                *
* eeprom_address - адрес в EEPROM                                *
* data -       данные                                    *
********************************************************************************************/
void writeEEPROM ( uint16_t address, uint16_t eeprom_address, uint16_t data ){
    address = address<<1;                   // сдвигаем на один влево (т.к. датчик принимает адрес + 1 бит чтение/запись)
      I2C1->CR1 |= I2C_CR1_START;
      while (!(I2C1->SR1 & I2C_SR1_SB)){}
      (void) I2C1->SR1;
      I2C1->DR = address | MLX90614_WRITE;         // режим записи , передача адреса MLX90614
      while (!(I2C1->SR1 & I2C_SR1_ADDR)){}
      (void) I2C1->SR1;
      (void) I2C1->SR2;
      I2C1->DR= eeprom_address | MLX90614_EEPROM_ACCESS;  // передача адреса EEPROM датчика MLX90614
      while (!(I2C1->SR1 & I2C_SR1_TXE)){}
      I2C1->DR =  ( uint8_t ) ( data & 0x00FF );      // пишем младший байт
      while(!(I2C1->SR1 & I2C_SR1_BTF)){}
      I2C1->DR = ( uint8_t ) ( data >> 8 )  ;      // пишем старший байт
      while(!(I2C1->SR1 & I2C_SR1_BTF)){}
      I2C1->CR1 |= I2C_CR1_PEC;              // посылаем PEC
      I2C1->CR1 |= I2C_CR1_STOP;
      return ;
}
/********************************************************************************************
* Чтение адреса датчика из EEPROM                              *
*                                               *
* Входные данные:                                      *
* address - адрес датчика                                  *
*                                              *
* Выходные данные:                                      *
* адрес в формате uint8_t                                  *
*                                              *
*******************************************************************************************/
uint16_t getAddrFromEEPROM ( uint16_t address ){
  uint16_t addr_eeprom;
  addr_eeprom = readEEPROM( address, MLX90614_SMBUS_ADDRESS );
  return addr_eeprom;
}
/********************************************************************************************
* Запись нового адреса датчика в EEPROM                          *
*                                               *
* Входные данные:                                      *
* address -     текущий адрес                               *
* new_address -  новый адресс                              *
*                                               *
* Возвращает 1 - успешно/ 0 - неудача                            *
********************************************************************************************/
int setAddrToEEPROM ( uint16_t address, uint16_t new_address ){
  uint16_t addr;
  writeEEPROM ( address, MLX90614_SMBUS_ADDRESS, 0x0); // стираем ячейку
  delay_ms(10);
  writeEEPROM (address, MLX90614_SMBUS_ADDRESS, new_address ); // пишем новый адрес
  delay_ms(10);
  addr = readEEPROM ( address, MLX90614_SMBUS_ADDRESS ); // читаем для сравнения
  if ( addr == new_address){
    return 1;
  }
  else return 0;
}
clk_ini.h
#ifndef INC_CLK_INI_H_
#define INC_CLK_INI_H_
#include "stm32f1xx.h"
int clk_ini(void);
#endif /* INC_CLK_INI_H_ */
clk_ini.c
#include "clk_ini.h"
int clk_ini(void){
  RCC->CR |= (1 << RCC_CR_HSEON_Pos);
  __IO int startCounter;
  for(startCounter = 0; ; startCounter++){
    if(RCC->CR & (1 << RCC_CR_HSERDY_Pos)){
      break;
    }// if
    if(startCounter > 0x1000){
      RCC->CR &= ~(1 << RCC_CR_HSEON_Pos);
      return 1;
    }
  }// for
  RCC->CFGR |= (0x07 << RCC_CFGR_PLLMULL_Pos) // PLL x9
        |(0x01 << RCC_CFGR_PLLSRC_Pos); // start clocking PLL of HSE
  RCC->CR |= (1 << RCC_CR_PLLON_Pos);
  for(startCounter = 0; ; startCounter++){
    if(RCC->CR & (1 << RCC_CR_PLLRDY_Pos)){
      break;
    }//if
    if(startCounter > 0x1000){
      RCC->CR &= ~(1 << RCC_CR_HSEON_Pos);
      RCC->CR &= ~(1 << RCC_CR_PLLON_Pos);
      return 2;
    }// if
  }// for
  ////////////////////////////////////////////////////////////
    //Настраиваем FLASH и делители
    ////////////////////////////////////////////////////////////
    //Устанавливаем 2 цикла ожидания для Flash
    //так как частота ядра у нас будет 48 MHz < SYSCLK <= 72 MHz
    FLASH->ACR |= (0x02<<FLASH_ACR_LATENCY_Pos);
    //Делители
    RCC->CFGR |= (0x00<<RCC_CFGR_PPRE2_Pos) //Делитель шины APB2 равен 1
              | (0x04<<RCC_CFGR_PPRE1_Pos) //Делитель нишы APB1 равен 2
              | (0x00<<RCC_CFGR_HPRE_Pos); //Делитель AHB отключен
    RCC->CFGR |= (0x02<<RCC_CFGR_SW_Pos); //Переключаемся на работу от PLL
    //Ждем, пока переключимся
    while((RCC->CFGR & RCC_CFGR_SWS_Msk) != (0x02<<RCC_CFGR_SWS_Pos))
    {
    }
    //После того, как переключились на
    //внешний источник такирования
    //отключаем внутренний RC-генератор
    //для экономии энергии
    RCC->CR &= ~(1<<RCC_CR_HSION_Pos);
    //Настройка и переклбючение сисемы
    //на внешний кварцевый генератор
    //и PLL запершилось успехом.
    //Выходим
  return 0;
}
delay.h
#ifndef DELAY_DELAY_H_
#define DELAY_DELAY_H_
#include "stm32f1xx.h"
#define F_CPU 72000000UL
#define US F_CPU/1000000
#define MS F_CPU/1000
#define SYSTICK_MAX_VALUE 16777215
#define US_MAX_VALUE SYSTICK_MAX_VALUE/(US)
#define MS_MAX_VALUE SYSTICK_MAX_VALUE/(MS)
void delay_us(uint32_t us); // до 233 мкс
void delay_ms(uint32_t ms); // до 233 мс
void delay_s(uint32_t s);
delay.c
#include "delay.h"
/* Функции задержек на микросекунды и миллисекунды*/
void delay_us(uint32_t us){ // до 233 016 мкс
  if (us > US_MAX_VALUE || us == 0)
    return;
  SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk; // запретить прерывания по достижении 0
  SysTick->CTRL |= SysTick_CTRL_CLKSOURCE_Msk; // ставим тактирование от процессора
  SysTick->LOAD = (US * us-1); // устанавливаем в регистр число от которого считать
  SysTick->VAL = 0; // обнуляем текущее значение регистра SYST_CVR
  SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; // запускаем счетчик
  while(!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk)); // ждем установку флага COUNFLAG в регистре SYST_CSR
  SysTick->CTRL &= ~SysTick_CTRL_COUNTFLAG_Msk;  // скидываем бит COUNTFLAG
  SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; // выключаем счетчик
  return;
}
void delay_ms(uint32_t ms){ // до 233 мс
  if(ms > MS_MAX_VALUE || ms ==0)
    return;
  SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk;
  SysTick->CTRL |= SysTick_CTRL_CLKSOURCE_Msk;
  SysTick->LOAD = (MS * ms);
  SysTick->VAL = 0;
  SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
  while(!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk));
  SysTick->CTRL &= ~SysTick_CTRL_COUNTFLAG_Msk;
  SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
return;
}
void delay_s(uint32_t s){
  for(int i=0; i<s*5;i++) delay_ms(200);
  return;
}
lcd_20x4.h
#ifndef LCD_LCD_20X4_2004A_LCD_20X4_H_
#define LCD_LCD_20X4_2004A_LCD_20X4_H_
#include "stm32f1xx.h"
#include "delay.h"
// display commands
#define CLEAR_DISPLAY 0x1
#define RETURN_HOME 0x2
#define ENTRY_MODE_SET 0x6 // mode cursor shift rihgt, display non shift
#define DISPLAY_ON 0xC // non cursor
#define DISPLAY_OFF 0x8
#define CURSOR_SHIFT_LEFT 0x10
#define CURSOR_SHIFT_RIGHT 0x14
#define DISPLAY_SHIFT_LEFT 0x18
#define DISPLAY_SHIFT_RIGHT 0x1C
#define DATA_BUS_4BIT_PAGE0 0x28
#define DATA_BUS_4BIT_PAGE1 0x2A
#define DATA_BUS_8BIT_PAGE0 0x38
#define SET_CGRAM_ADDRESS 0x40 // usage address |= SET_CGRAM_ADDRESS
#define SET_DDRAM_ADDRESS 0x80
// положение битов в порте ODR
#define PIN_RS 0x1
#define PIN_EN 0x2
#define PIN_D4 0x1000
#define PIN_D5 0x2000
#define PIN_D6 0x4000
#define PIN_D7 0x8000
#define     LCD_PORT                 GPIOB
#define    LCD_ODR         LCD_PORT->ODR
#define     LCD_PIN_RS()         LCD_PORT->CRL |= GPIO_CRL_MODE0_0;\
                  LCD_PORT->CRL &= ~GPIO_CRL_CNF0; // PB0  выход тяни-толкай, частота 10 Мгц
#define     LCD_PIN_EN()            LCD_PORT->CRL |= GPIO_CRL_MODE1_0;\
                  LCD_PORT->CRL &= ~GPIO_CRL_CNF1;        // PB1
#define     LCD_PIN_D4()            LCD_PORT->CRH |= GPIO_CRH_MODE12_0;\
                  LCD_PORT->CRH &= ~GPIO_CRH_CNF12;          // PB7
#define     LCD_PIN_D5()             LCD_PORT->CRH |= GPIO_CRH_MODE13_0;\
                  LCD_PORT->CRH &= ~GPIO_CRH_CNF13;      // PB6
#define     LCD_PIN_D6()            LCD_PORT->CRH |= GPIO_CRH_MODE14_0;\
                  LCD_PORT->CRH &= ~GPIO_CRH_CNF14;         // PB5
#define     LCD_PIN_D7()            LCD_PORT->CRH |= GPIO_CRH_MODE15_0;\
                  LCD_PORT->CRH &= ~GPIO_CRH_CNF15;         // PB10
#define     LCD_PIN_MASK   (PIN_RS | PIN_EN | PIN_D7 | PIN_D6 | PIN_D5 | PIN_D4) // 0b0000000011110011 маска пинов для экрана
void lcd_2004a_init(void); // инициализация ножек порта под экран
void sendByte(char byte, int isData);
void sendStr(char *str, int row , int position); // вывод строки
#endif /* LCD_LCD_20X4_2004A_LCD_20X4_H_ */
lcd_20x4.c
#include "lcd_20x4.h"
// посылка байта в порт LCD
void lcdInit(void); // инициализация дисплея
void sendByte(char byte, int isData){
  //обнуляем все пины дисплея
  LCD_ODR &= ~LCD_PIN_MASK;
  if(isData == 1) LCD_ODR |= PIN_RS; // если данные ставмим RS
    else LCD_ODR &= ~(PIN_RS);       // иначе скидываем RS
  // поднимаем пин E
    LCD_ODR |= PIN_EN;
  // ставим старшую тетраду на порт
  if(byte & 0x80) LCD_ODR |= PIN_D7;
  if(byte & 0x40) LCD_ODR |= PIN_D6;
  if(byte & 0x20) LCD_ODR |= PIN_D5;
  if(byte & 0x10) LCD_ODR |= PIN_D4;
  LCD_ODR &= ~PIN_EN; // сбрасываем пин Е
  //обнуляем все пины дисплея кроме RS
  LCD_ODR &= ~(LCD_PIN_MASK & ~PIN_RS);
  // поднимаем пин E
    LCD_ODR |= PIN_EN;
  // ставим младшую тетраду на порт
  if(byte & 0x8) LCD_ODR |= PIN_D7;
  if(byte & 0x4) LCD_ODR |= PIN_D6;
  if(byte & 0x2) LCD_ODR |= PIN_D5;
  if(byte & 0x1) LCD_ODR |= PIN_D4;
  // сбрасываем пин Е
  LCD_ODR &= ~(PIN_EN);
  delay_us(40);
  return;
}
// функция тактирует порт под дисплей и задает пины на выход тяни толкай и частоту 50 Мгц
void lcd_2004a_init(void){
//----------------------включаем тактирование порта----------------------------------------------------
  if(LCD_PORT == GPIOB) RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
  else if (LCD_PORT == GPIOA) RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
  else return;
//--------------------- инициализация пинов для LCD-----------------------------------------------------
    LCD_PIN_RS();
    LCD_PIN_EN();
    LCD_PIN_D7();
    LCD_PIN_D6();
    LCD_PIN_D5();
    LCD_PIN_D4();
    lcdInit();
  return ;
}
//--------------------- инициализация дисплея-----------------------------------------------------------
void lcdInit(void){
      delay_ms(200); // ждем пока стабилизируется питание
      sendByte(0x33, 0); // шлем в одном байте два 0011
      delay_us(120);
      sendByte(0x32, 0); // шлем в одном байте  00110010
      delay_us(40);
      sendByte(DATA_BUS_4BIT_PAGE0, 0); // включаем режим 4 бит
      delay_us(40);
      sendByte(DISPLAY_OFF, 0); // выключаем дисплей
      delay_us(40);
      sendByte(CLEAR_DISPLAY, 0); // очищаем дисплей
      delay_ms(2);
      sendByte(ENTRY_MODE_SET, 0); //ставим режим смещение курсора экран не смещается
      delay_us(40);
      sendByte(DISPLAY_ON, 0);// включаем дисплей и убираем курсор
      delay_us(40);
  return ;
}
void sendStr( char *str, int row , int position ){
  char start_address;
  switch (row) {
  case 1:
    start_address = 0x0; // 1 строка
    break;
  case 2:
    start_address = 0x40; // 2 строка
    break;
  case 3:
    start_address = 0x14; // 3 строка
    break;
  case 4:
    start_address = 0x54; // 4 строка
    break;
  }
  start_address += position; // к началу строки прибавляем позицию в строке
  sendByte((start_address |= SET_DDRAM_ADDRESS), 0); // ставим курсор на начало нужной строки  в DDRAM
  delay_ms(4);
  while(*str != '\0'){
    sendByte(*str, 1);
    str++;
  }// while
}

===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_programmirovanie_mikrokontrollerov (Программирование микроконтроллеров), #_stm32f103, #_infrakrasnyj_datchik (инфракрасный датчик), #_mlx90614, #_stm32, #_programmirovanie_mikrokontrollerov (
Программирование микроконтроллеров
)
Профиль  ЛС 
Показать сообщения:     

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

Текущее время: 22-Ноя 13:29
Часовой пояс: UTC + 5