Умные часы своими руками за 1500 рублей. Часть 3 – программа на МК

в 9:34, , рубрики: Носимая электроника

Умные часы своими руками за 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);

Исходники программы тут

И еще фото новенького корпуса:
Умные часы своими руками за 1500 рублей. Часть 3 – программа на МК
Умные часы своими руками за 1500 рублей. Часть 3 – программа на МК

Как всегда жду с нетерпением вопросов и комментариев.

Часть 1 — Начало
Часть 2 — Плата и компоненты

Автор: winKING

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js