Русский
Русский
English
Статистика
Реклама

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

Датчик 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 массив, переданный по ссылке. Удобно применять для вывода температуры на LCD

uint16t getAddrFromEEPROM( uint16t address )

Возвращает адрес датчика, записанный в его EEPROM. Принимает текущий адрес датчика.

int setAddrToEEPROM ( uint16t address, uint16t new_address )

Записывает адрес датчика в EEPROM. Применяется для изменения адреса датчика.

uint16t readEEPROM( uint16t address, uint16t eepromaddress )

Универсальная функция чтения EEPROM

void writeEEPROM ( uint16t address, uint16t eepromaddress, uint16t data )

Универсальная функция записи в EEPROM

С заголовочным файлом закончили. Самое время написать реализации функций.

void mlx90614Init(void){ delay_ms(120);// на стабилизацию питания и переходные процессы в датчикеRCC->APB2ENR |= RCC_APB2ENR_IOPBEN;// тактируем портRCC->APB1ENR |= RCC_APB1ENR_I2C1EN;// тактируем i2c1GPIOB->CRL |= GPIO_CRL_MODE6 | GPIO_CRL_MODE7;// выход 50 мгцGPIOB->CRL |= GPIO_CRL_CNF6 | GPIO_CRL_CNF7; // альтернативная ф-я открытый стокI2C1->CR2 &= ~I2C_CR2_FREQ; // скидываем биты частоты шины тактирования  APB1I2C1->CR2 |= F_APB1; // устанавливаем частоту шины APB1 от которой тактируется I2C модульI2C1->CR1 &= ~I2C_CR1_PE; // выключаем модуль I2C для настройки регистра CCRI2C1->CCR &= ~I2C_CCR_CCR;I2C1->CCR |=  CCR_VALUE;I2C1->TRISE |=  TRISE_VALUE;I2C1->CR1 |= I2C_CR1_ENPEC; // разрешаем отсылку PECI2C1->CR1 |= I2C_CR1_PE;// включаем модуль I2CI2C1->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_ttemp ; // температура uint8_ttemp_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 датчика выглядит так:

  1. START

  2. Передаем адрес датчика, сдвинутый на 1 бит влево плюс бит записи (первый). Для записи - 0

  3. Передаем адрес RAM откуда читать плюс команда доступа к RAM. Адресом RAM может быть температура кристалла датчика или температура одного из двух инфракрасных сенсоров.

  4. Повторный START

  5. Передаем адрес датчика сдвинутый на 1 влево плюс бит чтения.

  6. Читаем младший байт

  7. Читаем старший байт

  8. STOP

Теперь мы умеем читать температуру из датчика. Осталось реализовать возможность менять адрес, чтобы можно было вешать несколько датчиков на шину. Но перед этим напишем две вспомогательные функции для работы с EEPROM для записи и чтения.

Алгоритм чтения из EEPROM выглядит следующим образом:

  1. START

  2. Передаем адрес датчика, сдвинутый на 1 бит влево плюс бит записи (первый). Для записи - 0

  3. Передаем адрес EEPROM откуда читать плюс команда доступа к EEPROM ( определена в заголовочном файле )

  4. Повторный START

  5. Передаем адрес датчика плюс бит чтения

  6. Читаем младший байт

  7. Читаем старший байт

  8. STOP

Функция чтения из EEPROM

uint16_t readEEPROM( uint16_t address, uint16_t eeprom_address ){ uint16_tdata_msb; uint16_tdata_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. Разница только в командах выбора памяти и адресах этой памяти.

С записью немного иначе. Алгоритм следующий:

  1. START

  2. Передаем адрес датчика, сдвинутый влево плюс бит записи

  3. Передаем адрес EEPROM плюс команда выбора EEPROM памяти

  4. Передаем младший байт

  5. Передаем старший байт

  6. Передаем PEC (байт контрольной суммы )

  7. 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:

  1. Включение питания

  2. Запись в ячейку нулей, тем самым эффективно стирая ее

  3. Ждем 10 миллисекунд

  4. Пишем новое значение ячейки

  5. Ждем еще 10 миллисекунд

  6. Читаем ячейку для сравнения с исходным значением, чтобы убедиться, что записано без ошибок

  7. Выключаем питание

От себя дополню. Новый адрес датчика будет использоваться только после выключения и повторного включения питания датчика. В связи с этим необходимо предусмотреть отдельный пин контроллера, который будет управлять включением и выключением датчика через, например, транзистор. Здесь возникает вопрос - зачем вообще нужен режим сна, если все равно для работы с 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(); // инициализация дисплея a2004mlx90614Init(); // инициализация I2C для датчикаuint16_t geted_eeprom_address;char char_eeprom_address[20];char crystal_temp[10];   // массив для строки температурыchar first_sensor_temp[10];// читаем адрес датчика из EEPROM и выводим на LCDgeted_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); // записываем новый адрес// снова читаем адрес и выводим на LCDgeted_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;// тактируем i2c1GPIOB->CRL |= GPIO_CRL_MODE6 | GPIO_CRL_MODE7;// выход 50 мгцGPIOB->CRL |= GPIO_CRL_CNF6 | GPIO_CRL_CNF7; // альтернативная ф-я открытый стокI2C1->CR2 &= ~I2C_CR2_FREQ; // скидываем биты частоты шины тактирования  APB1I2C1->CR2 |= F_APB1; // устанавливаем частоту шины APB1 от которой тактируется I2C модульI2C1->CR1 &= ~I2C_CR1_PE; // выключаем модуль I2C для настройки регистра CCRI2C1->CCR &= ~I2C_CCR_CCR;I2C1->CCR |=  CCR_VALUE;I2C1->TRISE |=  TRISE_VALUE;I2C1->CR1 |= I2C_CR1_ENPEC; // разрешаем отсылку PECI2C1->CR1 |= I2C_CR1_PE;// включаем модуль I2CI2C1->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_ttemp ; // температура uint8_ttemp_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_tdata_msb; uint16_tdata_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;}// ifif(startCounter > 0x1000){RCC->CR &= ~(1 << RCC_CR_HSEON_Pos);return 1;}}// forRCC->CFGR |= (0x07 << RCC_CFGR_PLLMULL_Pos) // PLL x9  |(0x01 << RCC_CFGR_PLLSRC_Pos); // start clocking PLL of HSERCC->CR |= (1 << RCC_CR_PLLON_Pos);for(startCounter = 0; ; startCounter++){if(RCC->CR & (1 << RCC_CR_PLLRDY_Pos)){break;}//ifif(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){ // до 233016 мксif (us > US_MAX_VALUE || us == 0)return;SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk; // запретить прерывания по достижении 0SysTick->CTRL |= SysTick_CTRL_CLKSOURCE_Msk; // ставим тактирование от процессораSysTick->LOAD = (US * us-1); // устанавливаем в регистр число от которого считатьSysTick->VAL = 0; // обнуляем текущее значение регистра SYST_CVRSysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; // запускаем счетчикwhile(!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk)); // ждем установку флага COUNFLAG в регистре SYST_CSRSysTick->CTRL &= ~SysTick_CTRL_COUNTFLAG_Msk;// скидываем бит COUNTFLAGSysTick->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#defineLCD_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"// посылка байта в порт LCDvoid lcdInit(void); // инициализация дисплеяvoid sendByte(char byte, int isData){//обнуляем все пины дисплеяLCD_ODR &= ~LCD_PIN_MASK;if(isData == 1) LCD_ODR |= PIN_RS; // если данные ставмим RSelse LCD_ODR &= ~(PIN_RS);   // иначе скидываем RS// поднимаем пин ELCD_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; // сбрасываем пин Е//обнуляем все пины дисплея кроме RSLCD_ODR &= ~(LCD_PIN_MASK & ~PIN_RS);// поднимаем пин ELCD_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); // шлем в одном байте два 0011delay_us(120);sendByte(0x32, 0); // шлем в одном байте  00110010delay_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); // ставим курсор на начало нужной строки  в DDRAMdelay_ms(4);while(*str != '\0'){sendByte(*str, 1);str++;}// while}
Источник: habr.com
К списку статей
Опубликовано: 18.11.2020 22:19:29
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Программирование микроконтроллеров

Stm32f103

Инфракрасный датчик

Mlx90614

Stm32

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru