Датчик 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 датчика выглядит так:
-
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_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. Разница только в командах выбора памяти и адресах этой памяти.
С записью немного иначе. Алгоритм следующий:
-
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(); // инициализация дисплея 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_ */
#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;}
#ifndef INC_CLK_INI_H_#define INC_CLK_INI_H_#include "stm32f1xx.h"int clk_ini(void);#endif /* INC_CLK_INI_H_ */
#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;}
#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);
#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;}
#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_ */
#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}