Розумні годинник своїми руками за 1500 рублів. Частина 3 - програма на МК

  
 
Ось і прийшов час викласти вихідний код прошивки МК, але спочатку я хочу подякувати bigbee за новенький корпус для моїх годин, який він видрукував і вислав мені абсолютно безкоштовно. Так само спасибі Chameleonka за пропозицію надрукувати корпус.
 
Отже, програма була написана на Code Compmser Studio V 5.3.0
Розписувати код досконально я не буду, але важливі моменти (на мій погляд) розпишу.
Відразу хочу обмовитися, що дана програма не ідеальна і не оптимізована.
 
Приступимо
 
У програму включений файл Symbols.h, що містить таблицю символів, іконки панелі повідомлень, цифри для годин. Всі дані представлені у вигляді масивів, в яких зберігаються дані для попиксельного відображення на екрані.
Так само включений файл TI_USCI_I2C_master.h. Це стандартна бібліотека від TI для роботи з модулем USCI в якості I2C master для прийому — передачі даних.
 Включення і визначення
#include <msp430g2553.h>
#include "Symbols.h"
#include "TI_USCI_I2C_master.h"

/* Bit operations */
#define BIT_SET(lval, mask)     ((lval) |= (mask))
#define BIT_CLR(lval, mask)     ((lval) &= ~(mask))
#define BIT_TEST(val, mask)     (((val) & (mask))==(mask))
/* BSL */
#define TXD		BIT1		// P1.1: BSL TxD
#define RXD		BIT5		// P1.5: BSL RxD
/* BT */
#define BT_TXD    	BIT1		// P2.1: UART BT TxD
#define BT_RXD    	BIT0		// P2.0: UART BT RxD
#define BT_PWR    	BIT2		// P2.2, P3.2
#define BT_LED    	BIT3		// P3.3
/* LCD */
#define PIN_RESET 	BIT2		// P1.2  RESET
#define PIN_SCE 	BIT3		// P1.3  CS
#define PIN_SDIN 	BIT4		// P1.4  SDA //mosi
#define PIN_SCLK 	BIT1		// P3.1  SCK
#define PIN_LED 	BIT0		// P3.0  подсветка дисплея
#define LCD_C		0		// Command
#define LCD_D		1		// Data
/* Buttons & vibro */
#define B_CENT		BIT4		// P2.4
#define B_UP		BIT3		// P2.3
#define B_DOWN		BIT5		// P2.5
#define vibro		BIT4		// P3.4
/* System configuration */
#define TIMER1A_CLOCK	1000000L			// Timer1_A clock rate (1 MHz)
#define UART_BAUD	9600				// desired UART baud rate
#define BT_BITTIME	(TIMER1A_CLOCK/UART_BAUD)	// Bit interval
#define BT_HALF_BT	((BT_BITTIME+1)/2)		// Half-bit interval
#define Slave_Address	0x68				// address RTC

 
 Визначення функцій
void check_akkum(void);					// проверка состояния аккумулятора
void check_bluetooth(void);				// проверка состояния BT

void uart_tx_bt(char c);				// отправка символа по BT
void uart_puts_bt(char const* s);			// отправка строки по BT

void get_time_from_rtc(void);				// получение времени с ЧРВ и запись во FLASH
void set_time_to_rtc(void);				// запись времени на ЧРВ

void LcdCharacter(char character);			// вывести символ
void LcdClear(void);					// отчистить весь экран
void clear_1(void);					// отчистить экран, кроме статус бара
void Lcd_set_pos(unsigned char c, unsigned char r);	// установить позицию в символах на строку
void Lcd_set_pos_pix(unsigned char c, unsigned char r);	// установить позицию в пикселях на строку
void lcd_contrast(unsigned char contrast2);		// установить контрастность экрана
void lcd_dig(unsigned char num, unsigned char pos_x, unsigned char pos_y);	// цифры часов
void lcd_dot(unsigned char num, unsigned char pos_x, unsigned char pos_y);	// двоеточия часов
void LcdWrite(unsigned char dc, unsigned char data);	// отправка комманды/данных на экран
void LcdString(char *characters);			// вывести строку
void lcd_show_sms(unsigned char a);			// значек смс
void lcd_show_call(unsigned char a);			// значек звонка
void lcd_show_bt(unsigned char a);			// значек BT
void lcd_show_bat(unsigned char proc);			// значек аккумулятора
void lcd_set_time_big(void);				// вывести большие часы
void lcd_set_time_small(void);				// вывести малые часы (в статус баре)
void lcd_show_main(void);				// показать главный экран (дата и большие часы)

void menu_setting(unsigned char submenu);		// меню
void down_sub_menu(void);				// передвижение по меню
void up_sub_menu(void);					//		и изменения в подменю
void parse_string(void);				// разбор принятой строки

 
Налаштовуємо модулі МК для роботи із зовнішніми модулями годин:
 
     
  • DCO — Частота вбудованого генератора 8MHz;
  •  
  • Timer0 — ШІМ для підсвічування дисплея;
  •  
  • Timer1 — Програмний UART для спілкування з BT;
  •  
  • WDT + — Інтервальний таймер для перевірок годин;
  •  
  • FLASH — Зберігання змінюваних налаштувань і дати / часу;
  •  
  • ADC10 — АЦП для визначення напруги на акумуляторі.
  •  
 Ініціалізація
WDTCTL = WDTPW + WDTHOLD;		// отключаем ватчдог
	// init clocks
	BCSCTL1 = CALBC1_8MHZ;
	DCOCTL = CALDCO_8MHZ;
	BCSCTL2 = 0;			// MCLK = 8MHz/1,SMCLK = 8MHz/1
	BCSCTL3 = LFXT1S_2;			// Mode 2 for LFXT1 : VLO = 12kHz

	__delay_cycles(800000);

	// сажаем BSL ноги на выход и лог. 0
	BIT_SET(P1DIR, TXD | RXD);
	BIT_CLR(P1OUT, TXD | RXD);

	edit_time = 0;
	// берем инфу о настройках экрана из FLASH'а
	char *a;
	a = (char*) 0x104d;
	contrast = *a++;
	pwm_width = 0x00ff | (*a++) << 8;
	timer_off = ((*a++) & 0x00ff) << 8;
	timer_off |= ((*a++) & 0x00ff);

	// настройка портов для LCD
	BIT_SET(P1DIR, PIN_RESET + PIN_SCE + PIN_SDIN);
	BIT_SET(P3DIR, PIN_SCLK);
	BIT_CLR(P1OUT, PIN_RESET + PIN_SDIN);
	BIT_CLR(P3OUT, PIN_SCLK);
	BIT_SET(P1OUT, PIN_SCE);
	BIT_SET(P3DIR, PIN_LED);
	BIT_SET(P3OUT, PIN_LED);
	BIT_SET(P1OUT, PIN_RESET);

	// последовательность инициализации LCD
	LcdWrite(LCD_C, 0xE2); 					// сброс програмный
	LcdWrite(LCD_C, 0x3D); 					// Charge pump ON
	LcdWrite(LCD_C, 0x01); 					// Charge pump=4
	LcdWrite(LCD_C, 0xA4); 					//
	LcdWrite(LCD_C, 0x2F); 					//
	LcdWrite(LCD_C, 0xC0); 					// нормальное верх-низ
	LcdWrite(LCD_C, 0xA0); 					// нормальное лево-право
	__delay_cycles(800000);
	LcdWrite(LCD_C, 0xAF); 					// Display ON

	LcdClear();
	lcd_contrast(contrast);
	BIT_SET(P3SEL, PIN_LED);
	BIT_SET(P3DIR, PIN_LED);

	// таймер ШИМ для подсветки
	TACTL = TASSEL_2 + ID_0 + MC_1 + TACLR;	// SMCLK/1 + прямой счет до TACCR0 + counter clear
	TACCR0 = 0x0fff;				// период ШИМ
	TACCR2 = pwm_width;				// заполнение ШИМ для подсветки
	TACCTL2 = OUTMOD_6;				// выход ШИМ

	// init Bluetooth
	BIT_SET(P3DIR, BT_PWR);
	BIT_CLR(P3REN, BT_PWR);
	BIT_SET(P3OUT, BT_PWR);
	BIT_SET(P2DIR, BT_PWR);
	BIT_CLR(P2REN, BT_PWR);
	BIT_SET(P2OUT, BT_PWR);				//питание БТ ON
	BIT_CLR(P3DIR, BT_LED);				//состояние бт
	bt_on = 1;
	lcd_show_bt(1);

	//Timer1 для uart
	TA1CTL = TASSEL_2 + ID_3 + MC_2 + TAIE;	//SMCLK/8 + Continous up + interrupt enable
	TA1CCTL1 = OUT; 				//Tx
	TA1CCTL0 = CM_2 + SCS + CAP + CCIE; 	//Rx

	//Порты Rx и TX для БТ
	BIT_SET(P2SEL, BT_TXD + BT_RXD);
	BIT_SET(P2DIR, BT_TXD);

	//таймер для выкл. экрана
	BIT_SET(TA1CCTL2, CCIE);

	// Настройка кнопок
	BIT_CLR(P2DIR, B_CENT|B_UP|B_DOWN);
	// направление пинов - IN
	BIT_SET(P2REN, B_CENT|B_UP|B_DOWN);
	// подключение резисторов
	BIT_SET(P2IE, B_CENT|B_UP|B_DOWN);
	// Разрешение прерываний
	BIT_SET(P2IES, B_CENT|B_UP|B_DOWN);
	// Прерывание происходит по 1/0 (отпусканию/нажатию)
	BIT_CLR(P2IFG, B_CENT|B_UP|B_DOWN);
	// Очистка флага прерываний

	// WDT+ как интервальный таймер, частота 12 КГц - ACLK - VLO
	WDTCTL = WDTPW + WDTTMSEL + WDTSSEL;
	BIT_SET(IE1, WDTIE);

	// vibro - выход
	BIT_SET(P3DIR, vibro);
	BIT_CLR(P3OUT, vibro);

	lcd_show_main();			// главный экран
	__enable_interrupt();
	edit_time = 0;
	get_time = 0;
	set_time = 0;
	get_time_from_rtc();		// обновляем время
	__delay_cycles(8000000);
	set_time_to_rtc();			// обновляем время

	// init ADC
	ADC10CTL0 = SREF_1 + ADC10SR + REF2_5V + ADC10SHT_2 + REFON + ADC10ON + ADC10IE;
	ADC10CTL1 = INCH_0; 
	ADC10AE0 |= 0x01; // устанавливаем 0-ой пин как вход для АЦП

 
Переривання
У всіх обробниках переривань спочатку відключаються інші переривання, які при завершенні обробника включаються назад.
При прийомі даних по BT відбувається переривання або Timer1 якщо годинник в активному режимі, або P2, якщо годинник в режимі енергозбереження.
У режимі LPM3 SMCLK відключений, відповідно таймер не працює. Якщо відбулося переривання порту P2 від BT, переводимо МК в активний режим і переводимо пін BT_RXD на вхід Timer1.
По закінченню прийому даних (символ кінця повідомлення 0x00) відбувається обробка отриманої рядка. Перший символ рядка — ідентифікатор. «1» — телефон підключився до годинах; «2» — вхідне смс; «3» — вхідний дзвінок; «4» — текст, який потрібно просто вивести на екран годин; «5» — з телефону відправлені дата і час.
 Розбір рядка
unsigned int il;
	unsigned char z,k;

	switch (inputString[0]) {
		case '1': {					// индикация успешно подключившегося к BT телефона
			BIT_SET(P3OUT, vibro);
			__delay_cycles(1600000);
			BIT_CLR(P3OUT, vibro);
			__delay_cycles(1600000);
			BIT_SET(P3OUT, vibro);
			__delay_cycles(1600000);
			BIT_CLR(P3OUT, vibro);
			__delay_cycles(1600000);
			BIT_SET(P3OUT, vibro);
			__delay_cycles(1600000);
			BIT_CLR(P3OUT, vibro);
			lcd_show_bt(2);
			bt_connect = 1;
			break;
		}
		case '2': {					// индикация входящего смс
			current_screen = 1;
			lcd_show_sms(1);
			lcd_show_call(0);
			clear_1();
			Lcd_set_pos(0, 1);
			z = 1;
			il = 1;
			k = 15;
			if (multiscreen) {
				current_screen = 4;
				while (il < 105) {
					LcdCharacter(inputString[il]);
					if (++il > k){
						Lcd_set_pos(0, ++z);
						k += 15;
					}
				}
				LcdCharacter(0x7f);
			} else
				while (inputString[il] != 0x00) {
					LcdCharacter(inputString[il]);
					if (++il > k){
						Lcd_set_pos(0, ++z);
						k += 15;
					}
				}
			BIT_SET(P3OUT, vibro);
			__delay_cycles(2800000);
			BIT_CLR(P3OUT, vibro);
			__delay_cycles(2400000);
			BIT_SET(P3OUT, vibro);
			__delay_cycles(8000000);
			BIT_CLR(P3OUT, vibro);
			break;
		}
		case '3': {					// индикация входящего звонка
			current_screen = 1;
			lcd_show_sms(0);
			lcd_show_call(1);
			clear_1();
			Lcd_set_pos(0, 2);
			il = 1;
			z = 2;
			k = 15;
			while (inputString[il] != 0x00) {
				LcdCharacter(inputString[il]);
				if (++il > k){
					Lcd_set_pos(0, ++z);
					k += 15;
				}
			}
			call_true = 1;
			BIT_SET(P3OUT, vibro);
			__delay_cycles(1600000);
			BIT_CLR(P3OUT, vibro);
			__delay_cycles(800000);
			BIT_SET(P3OUT, vibro);
			__delay_cycles(8000000);
			BIT_CLR(P3OUT, vibro);
			__delay_cycles(1600000);
			BIT_SET(P3OUT, vibro);
			__delay_cycles(1600000);
			BIT_CLR(P3OUT, vibro);
			__delay_cycles(800000);
			BIT_SET(P3OUT, vibro);
			__delay_cycles(8000000);
			BIT_CLR(P3OUT, vibro);
			break;
		}
		case '4': {					// отображение присланного текста
			current_screen = 1;
			lcd_show_sms(0);
			lcd_show_call(0);
			clear_1();
			Lcd_set_pos(0, 1);
			il = 1;
			z = 1;
			k = 15;
			if (multiscreen) {
				current_screen = 4;
				while (il < 105) {
					LcdCharacter(inputString[il]);
					if (++il > k){
						Lcd_set_pos(0, ++z);
						k += 15;
					}
				}
				LcdCharacter(0x7f);
			} else
				while (inputString[il] != 0x00) {
					LcdCharacter(inputString[il]);
					if (++il > k){
						Lcd_set_pos(0, ++z);
						k += 15;
					}
				}
			break;
		}
		case '5': {				  // сохранение присланного времени
			edit_time = 1;
			s10 = inputString[1] & 0x0f;
			s1 = inputString[2] & 0x0f;
			m10 = inputString[3] & 0x0f;
			m1 = inputString[4] & 0x0f;
			h10 = inputString[5] & 0x0f;
			h1 = inputString[6] & 0x0f;
			dw = (inputString[7] & 0x0f) + 1 ;
			d10 = inputString[8] & 0x0f;
			d1 = inputString[9] & 0x0f;
			mo10 = inputString[10] & 0x0f;
			mo1 = inputString[11] & 0x0f;
			ye10 = inputString[14] & 0x0f;
			ye1 = inputString[15] & 0x0f;
			set_time = 1;
			break;
		}
		default: {
			break;
		}
	}
	if (!multiscreen)
		for (il = 313; il > 0; il--)
			inputString[il] = 0;

 
Обробник переривання P2 так само перевіряє натиснення кнопок. Якщо кнопка натиснута, відбувається вихід з режиму енергозбереження. Так само для кожної кнопки в обробнику переривання P2 є цикл перевірки «довгого» натискання.
Обробник переривання WDT + перевіряє стан BT, дає команду для оновлення часу з ЧРВ, дає команду на включення АЦП.
Обробник переривання ADC10 дає команду для оновлення стану акумулятора. Переривання ADC10 спрацьовує після закінчення перетворення.
 Робота з FLASH
// запись данных во FLASH
	char *Flash_ptrC;					
	Flash_ptrC = (char *) 0x1040;             	// Point to beginning of seg C
	FCTL2 = FWKEY + FSSEL_1 + FN1;      	// MCLK/3 for Flash Timing Generator
	FCTL1 = FWKEY + ERASE;                    	// Set Erase bit
	FCTL3 = FWKEY ;                    		// Clear LOCK 
	*Flash_ptrC = 0x00;                  	// Dummy write to erase Flash seg C
	FCTL1 = FWKEY + WRT;                  	// Set WRT bit for write operation
	Flash_ptrC = (char *) 0x1040;        	// Point to beginning
	*Flash_ptrC++ = s10;			// 0x1040
	*Flash_ptrC++ = s1;				// 0x1041
	*Flash_ptrC++ = m10;			// 0x1042
	*Flash_ptrC++ = m1;				// 0x1043
	*Flash_ptrC++ = h10;			// 0x1044
	*Flash_ptrC++ = h1;				// 0x1045
	*Flash_ptrC++ = dw;				// 0x1046
	*Flash_ptrC++ = d10;			// 0x1047
	*Flash_ptrC++ = d1;				// 0x1048
	*Flash_ptrC++ = mo10;			// 0x1049
	*Flash_ptrC++ = mo1;			// 0x104a
	*Flash_ptrC++ = ye10;			// 0x104b
	*Flash_ptrC++ = ye1;			// 0x104c
	*Flash_ptrC++ = contrast;			// 0x104d
	*Flash_ptrC++ = (pwm_width & 0xff00) >> 8;	// 0x104e
	*Flash_ptrC++ = (timer_off & 0xff00) >> 8;	// 0x104f
	*Flash_ptrC++ = (timer_off & 0x00ff);	// 0x1050
	FCTL1 = FWKEY;                            	// Clear WRT bit
	FCTL3 = FWKEY + LOCK;             		// Set LOCK 

	// чтение данных из FLASH
	char *a;							
	a = (char*) 0x1040;
	s10 = *a++;
	s1 = *a++;
	m10 = *a++;
	m1 = *a++;
	h10 = *a++;
	h1 = *a++;
	dw = *a++;
	d10 = *a++;
	d1 = *a++;
	mo10 = *a++;
	mo1 = *a++;
	ye10 = *a++;
	ye1 = *a++;
	contrast = *a++;
	pwm_width = 0x00ff | (*a++) << 8;
	timer_off = ((*a++) & 0x00ff) << 8;
	timer_off |= ((*a++) & 0x00ff);

 
 
Ісходникі програми тут
 
І ще фото новенького корпусу:
 
 
 
Як завжди чекаю з нетерпінням питань і коментарів.
 
 Частина 1 — Початок
 Частина 2 — Плата і компоненти
  
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.