Начинаем продвижение по созданию усилителя. Первым делом я решил собирать мозги, так как именно от них зависит весь функционал периферии и эта часть имеет прямое отношение к тематике блога. Итак, что мы имеем.
- PIC16f877a
- DS1307
- DS18b20
Что требуется получить в итоге?
В первом приближении создаем просто базу с часами реального времени, термометром (снятие показаний температуры внутри корпуса, для регулировки кулера), USARTом, и эта база снабжена выводами под все возможные порты, для последующего расширения функционала – этакий конструктор для периферии усилителя.
Для начала определимся с протоколами, в данном случае мы используем три протокола передачи данных:
- I2C – главный протокол общения;
- 1-wire – связь с цифровым термометром DS18b20;
- USART – связь с компьютером.
Как всегда дело начинается с составления схемы:
По схеме из особенных моментов можно выделить 2:
- Разъемы Р2, Р14 предусмотрены для питания платы напрямую, без стабилизации, либо с внутренней стабилизацией.
- На разъем В предусмотрена возможность подключения кнопки к любому выводу либо использования его как обычный порт ввода/вывода.
В итоге получаем новую devboard с pic16f877a на борту, модернизируя которую можем добавлять новые функции для усилителя в любой момент. Ну еще пару фоток получившейся платы:
Надо будет освоить технологию лужения в домашних условиях, а то паяльником водить не сильно красиво получается…
Пока все разъемы не запаивал, т.к. в них еще нет необходимости, вот когда буду в корпус засовывать припаяю надежные разъемы, с помощью которых провода можно надежно зажать.
В принципе по схеме все, больше никаких особенностей – все стандартно, так что можно переходить к описанию программной реализации.
Начнем с I2C: можно было бы пойти проторенным путем софтварного i2c, но зачем растрачивать драгоценные ресурсы мк, если у нас на борту есть хардварный i2c? Все настройки можно выудить в даташите (ну или инете), поэтому, на этот раз не будет пространственных разъяснений, а сразу приступаем к делу.
Выносим наши функции в отдельный файл i2c.c:
#include <htc.h> #include "i2c.h" void I2CInit(void){ //Инициализация TRISC3 = 1; //SDA и SCL TRISC4 = 1; SSPSTAT |= 0x80; SSPCON = 0x28; // Режим ведущего, clock = FOSC/(4 * (SSPADD + 1)) SSPADD = 0x28; // 100Khz @ 4Mhz Fosc } void I2CStart(){ SEN = 1; // Стартовые условия while(SEN); } void I2CStop(){ // Процедура окончания передачи PEN = 1; // Включаем условия окончания передачи while(PEN); } void I2CRestart(){ // Повторный старт RSEN = 1; while(RSEN); } void I2CAck(){ // Шлем Ack бит ACKDT = 0; ACKEN = 1; while(ACKEN); } void I2CNak(){ // Шлем Nack бит ACKDT = 1; ACKEN = 1; while(ACKEN); } void I2CWait(){ // Растяжка клока while ( ( SSPCON2 & 0x1F ) || ( SSPSTAT & 0x04 ) ); } void I2CSend(unsigned char dat){ // Процедура отсылки данных SSPBUF = dat; // Запихиваем наш байт в буфер while(BF); // Ждем пока все данные выпихнутся из буфера I2CWait(); } unsigned char I2CRead(void){ // Процедура чтения unsigned char temp; RCEN = 1; // Разрешаем прием данных while(!BF); // Ждем пока заполнится буффер temp = SSPBUF; // Присваиваем нашей переменной значение из буфера I2CWait(); return temp; }
В заголовочном файле просто объявления функций:
void I2CInit(void); void I2CStart(); void I2CStop(); void I2CRestart(); void I2CAck(); void I2CNak(); void I2CWait(); void I2CSend(unsigned char dat); unsigned char I2CRead(void);
Дело сделано, идем дальше.
На борту есть часы реального времени DS1307, создадим функции общения с ними на базе, созданных только что команд для I2C. Но чтобы все было уже совсем красиво, вынесем все функции, которые были разработаны в прошлой статье о RTC. Привожу содержимое файла rtc.c:
#include <htc.h> #include <stdio.h> #include "usart.h" #include "rtc.h" #include "i2c.h" unsigned char BCDconv (unsigned char source) { unsigned char temp_min=0; unsigned char temp_maj=0; temp_min = source&15; temp_maj = source >> 4; temp_maj *= 10; return temp_maj+temp_min; } unsigned char DCBconv (unsigned char source) { unsigned char temp_min=0; unsigned char temp_maj=0; temp_maj = source/10 ; temp_min = source - temp_maj*10; temp_maj <<= 4; return temp_maj+temp_min; } void SetMin(unsigned char minutes) { //записываем значение в минуты I2CStart(); I2CSend(0b11010000); I2CSend(0x01); I2CSend(DCBconv(minutes)); I2CStop(); } unsigned char ReadMin() { unsigned char temp = 0; I2CStart(); //читаем значение минут I2CSend(0b11010000); I2CSend(0x01); I2CRestart(); I2CSend(0b11010001); temp = I2CRead(); I2CNak(); I2CStop(); return BCDconv(temp); } void SetHour(unsigned char hours) { //записываем значение в часы I2CStart(); I2CSend(0b11010000); I2CSend(0x02); I2CSend(DCBconv(hours)); I2CStop(); } unsigned char ReadHour() { unsigned char temp = 0; I2CStart(); //читаем значение часов I2CSend(0b11010000); I2CSend(0x02); I2CRestart(); I2CSend(0b11010001); temp = I2CRead(); I2CNak(); I2CStop(); return BCDconv(temp); } void SetSeconds(unsigned char seconds) { //записываем значение в секунды I2CStart(); I2CSend(0b11010000); I2CSend(0x00); I2CSend(DCBconv(seconds)); I2CStop(); } unsigned char ReadSeconds() { unsigned char temp = 0; I2CStart(); //читаем значение минут I2CSend(0b11010000); I2CSend(0x00); I2CRestart(); I2CSend(0b11010001); temp = I2CRead(); I2CNak(); I2CStop(); return BCDconv(temp); } void ShowTime() { printf("\fTime - %d :", ReadHour()); printf(" %d :", ReadMin()); printf(" %d ", ReadSeconds()); printf("\r\n*********************"); printf("\r\n Menu"); printf("\r\n 1 - Reload time"); printf("\r\n 2 - Setup time"); } void SetupTime() { unsigned char state = 1; unsigned char temp_min = 0; unsigned char temp_maj = 0; unsigned char temp = 0; while(state){ printf("\fEnter hours - "); temp_maj = getch(); printf(" %c", temp_maj); temp_min = getch(); printf("%c", temp_min); temp = getch(); if (temp == 13) { temp_maj -= 48; temp_min -= 48; temp = temp_maj*10 + temp_min; if (temp < 24) { SetHour(temp); state = 0; } } } state = 1; while(state){ printf("\fEnter minutes - "); temp_maj = getch(); printf(" %c", temp_maj); temp_min = getch(); printf("%c", temp_min); temp = getch(); if (temp == 13) { temp_maj -= 48; temp_min -= 48; temp = temp_maj*10 + temp_min; if (temp < 61) { SetMin(temp); state = 0; } } } state = 1; while(state){ printf("\fEnter seconds - "); temp_maj = getch(); printf(" %c", temp_maj); temp_min = getch(); printf("%c", temp_min); temp = getch(); if (temp == 13) { temp_maj -= 48; temp_min -= 48; temp = temp_maj*10 + temp_min; if (temp < 61) { SetSeconds(temp); state = 0; } } } ShowTime(); }
Код расписывать подробно не буду, т.к. уже раньше в упомянутой статье это делал. Точно также по поводу уарта – там все подробно расписано. Также оставлена процедура вывода через терминал на комп, на всякий пожарный.
Что ж, осталось добить термометр 🙂
Опять же, у нас есть готовая база для общения с DS18b20. Поэтому берем и просто добавляем нужные вставки в код (его я еще раз приводить не буду, не вижу смысла).
Добиваем код главной функции:
void main() { unsigned char input; //переменная для уарта unsigned char temp; //переменая для всякой фигни :) TRISB = 0x00; init_comms(); //готовим уарт I2CInit(); //готовим I2C printTemp(); //выкатываем сразу значение температуры for (;;) { input = getch(); switch (input) { case 49 : ShowTime(); //если нажата 1 то в функция отображения в терминале break; case 50 : SetupTime(); //если нажата 2 - то процедура установки часов через терминал break; case 51 : //если нажата 3 - то последовательно выбрасываем каждый тип времени, каждому типу присвоен свой уникальный цифровой индикатор GIE = 0; temp = ReadHour(); if (temp <10) printf("\r\n1010%d", temp); else printf("\r\n101%d", temp); __delay_ms(20); temp = ReadMin(); if (temp <10) printf("\r\n1020%d", temp); else printf("\r\n102%d", temp); __delay_ms(20); temp = ReadSeconds(); if (temp <10) printf("\r\n1030%d", temp); else printf("\r\n103%d", temp); __delay_ms(20); break; case 52 : printTemp(); //если нажата 4 - то выбрасываем значение температуры со своим уникальным идентификатором break; } } }
Проект для mplab можно взять тут.
Все готово для тестирования:
В видео видно, что по нажатию на кнопки выкатываются значния с цифрами 100-200 в начале, я их добавил, чтобы было легко парсить данные при выводе на gui нашего интерфейса. Собственно о начале интерфейса, в качестве базы я взял проект из недавней статьи и немножко дополнил его.
Добавил пару кнопок в гуевину:
Как видите, добавилась група “Sensors”, куда выкатываем показания времени и температуры. В обработку прерывания поступающих на порт данных, добавились следующие строки:
uint temp = temp_data.toUInt(); //Заводим численную переменную для обработки qDebug() << "temp/100 is: " << temp/100; //Вывод в режиме отладки, на всякий случай switch (temp/100) { //здесь происходит рассортировка данных case 101 : ui->lbHour->setText(QString::number(temp-((temp/100))*100)); //часы break; case 102 : ui->lbMin->setText(QString::number(temp-((temp/100))*100)); //минуты break; case 103 : ui->lbSec->setText(QString::number(temp-((temp/100))*100)); //секунды break; case 201 : ui->lbSign->setText("+"); ui->lbTemp->setText(QString::number(temp-((temp/100))*100)); //целое значение температуры break; case 202 : ui->lbSign->setText("-"); ui->lbTemp->setText(QString::number(temp-((temp/100))*100)); //целое значение температуры break; case 203 : ui->lbTempDrob->setText(QString::number(temp-((temp/100))*100)); //дробное значение температуры break; }
Видео с тест-драйвом:
На видео видны прострелы в работе, связанные с хреново написанной процедурой обработки данных с компа. Но это я буду править попозже, пока останавливаюсь на этой базе и перехожу к следующей ступени проекта.
Добавил содержание файла i2c.h
Не хватает заголовочного файла rtc.h, ну или я плохо смотрю 🙂