[Программирование микроконтроллеров] STM32 и бесконтактный датчик температуры MLX90614. Подключение по I2C
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Датчик 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
===========
Похожие новости:
- [Программирование, C++, C, Программирование микроконтроллеров] Включаем периферию контроллера за 1 такт или магия 500 строк кода
- [Программирование микроконтроллеров, Схемотехника, Производство и разработка электроники] Как мы контроллер управления элементами наружной рекламы делали (часть 2)
- [Программирование микроконтроллеров, DIY или Сделай сам] Учим железки разговаривать, или ESP32 DAC и немного таймера
- [Программирование микроконтроллеров] Сравнение компиляторов ARMCC, IAR и GCC
- [Промышленное программирование, Разработка робототехники, Программирование микроконтроллеров, Производство и разработка электроники] ModBus Slave RTU/ASCII без смс и регистрации. Версия 3
- [Программирование микроконтроллеров, Разработка на Raspberry Pi, DIY или Сделай сам, Здоровье] Затерянные в тумане, или Увлекательные приключения в мире АПР *
- [Программирование микроконтроллеров] Разбираемся с модулем ШИМ на tms320
- [Ненормальное программирование, Assembler, Программирование микроконтроллеров] Excel как транслятор в ассемблер AVR
- [Open source, Системное программирование, Программирование микроконтроллеров, Процессоры] О кэшах в микроконтроллерах ARM
- [Промышленное программирование, Разработка робототехники, Программирование микроконтроллеров, Разработка для интернета вещей, Производство и разработка электроники] ModBus Slave RTU/ASCII без смс и регистрации
Теги для поиска: #_programmirovanie_mikrokontrollerov (Программирование микроконтроллеров), #_stm32f103, #_infrakrasnyj_datchik (инфракрасный датчик), #_mlx90614, #_stm32, #_programmirovanie_mikrokontrollerov (
Программирование микроконтроллеров
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 19:32
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Датчик 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) //---------------- 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_ */ 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 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; } 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 ; }
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; }
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 ; } uint16_t getAddrFromEEPROM ( uint16_t address ){
uint16_t addr_eeprom; addr_eeprom = readEEPROM( address, MLX90614_SMBUS_ADDRESS ); return addr_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; } #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); } } #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; // тактируем 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; } #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; }// 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; } #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){ // до 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; } #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_ */ #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 =========== Похожие новости:
Программирование микроконтроллеров ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 19:32
Часовой пояс: UTC + 5