Без слежки и закладок. Как собрать свой мобильник и почему это проще, чем кажется
Источник: t.me/Bureau121
Содержание статьи
- Блок-схема компонентов
- Настраиваем UART
- Работа с экраном
- Клавиатура
- Модуль GSM
- Телефонная книга
- Работа с графикой
- Энергосбережение
- Интерфейс
- Идеи и развитие проекта
Все началось с того, что некоторое время назад я собирал для себя телефон на модуле связи GSM. Современная электроника была размещена в винтажном корпусе с внешней трубкой и дисковым номеронабирателем (помнишь ли ты еще, как ими пользоваться?). Увы, из-за непродуманной схемы звонить с него было неудобно.
Тогда я решил предпринять вторую попытку, но уже с новой концепцией. Хотелось создать компактное устройство, выполняющее функции простого кнопочного телефона и при этом пригодное к практическому использованию. В идеале заряда даже от небольшого аккумулятора должно хватать минимум на неделю. Никаких лишних сервисов, подозрительных приложений и надоедливых уведомлений, только самое необходимое — звонки, SMS и телефонная книжка.
Проект демонстрировался на Chaos Constructions 2019 и, к моему (приятному) удивлению, вызвал интерес у широкой аудитории. Многим было любопытно узнать внутреннее устройство мобильного телефона, поэтому сегодня я подробно расскажу, как можно собрать подобный гаджет самостоятельно.
Блок-схема компонентов
Сперва определимся с требованиями к устройству: нам нужно совершать исходящие звонки, принимать входящие, читать и писать SMS (в том числе на кириллице) и управлять контактами в телефонной книге. Это базовая функциональность, которую пользователи ожидают от кнопочных телефонов. Конечно, это далеко не полный список и тут не хватает как минимум встроенных игр (змейки или тетриса), но их легко будет добавить уже на финальном этапе.
Ключевым компонентом устройства станет модуль сотовой связи SIM800C. Он содержит полный радиотракт, аудиотракт и реализует основные функции работы с сетью GSM. Иными словами, это практически готовый мост GSM-UART, который нуждается лишь в управлении через внешний терминал.
Для этого нам потребуется экран, клавиатура и какой-нибудь микроконтроллер для выполнения основной программы. В качестве экрана я использовал дисплейный модуль ST7735 с разрешением 128 на 160 пикселей. К нему у меня уже была готовая библиотека, которая позволяла отрисовывать символы и графические примитивы. По большому счету выбор дисплея некритичен для проекта, и ты можешь использовать любой другой с подходящей диагональю.
Клавиатура с шестнадцатью кнопками реализована на сдвиговых регистрах (пара восьмибитных микросхем 74HC165 (PDF). Также ты можешь использовать их отечественный аналог — микросхемы компании «Интеграл» КР1533ИР9. В любом случае выход таких регистров представляет собой неполноценный SPI, так как даже при отключении они не переходят в высокоимпедансное состояние. Поэтому вместо аппаратной и совмещенной с дисплеем шины SPI для них использовалась программная реализация.
Управлять всем будет микроконтроллер семейства STM32. Так как особого быстродействия не требуется, подойдут даже бюджетные решения. Я остановил свой выбор на F103C8T6 (PDF), его ресурсов тут должно хватить с избытком. Кроме того, именно на таком микроконтроллере выпускается известная модельная линейка отладочных плат BluePill (прекрасное средство для избавления от Arduino-зависимости). Это позволило собрать прототип и протестировать работу компонентов практически с самого старта.
Позже (и в качестве приятного бонуса) я решил добавить в проект внешнюю постоянную память W25Q32 (PDF) на 32 Мбит. Это позволило не перезаписывать флеш самого микроконтроллера и хранить все контакты отдельно. Кроме того, появилась возможность загружать на телефон картинки, символы и прочие элементы растровой графики.
Сама схема мобильного телефона достаточно стандартная и в комментариях вряд ли нуждается. SIM800C включается при подаче низкого уровня на вывод REST (используется транзистор Q1, соединенный с контактом PA0 микроконтроллера). Дополнительно светодиоды VD2 и VD3 указывают на состояние радиомодуля. VD2 мигает при успешном подключении, тогда как VD3 горит все время, пока SIM800C активен.
Компоненты размещены на двух односторонних печатных платах, преимущественно поверхностным монтажом. Первая плата содержит радиомодуль, микроконтроллер, микросхему внешней памяти и разъемы для подключения антенны и динамика. Вторая плата целиком и полностью отдана под клавиатуру. Собранная конструкция помещается в корпус из оргстекла и закрепляется на стойках М3.
Питается наше устройство от литий-полимерного аккумулятора на 1500 мА · ч. Его емкость примерно в два раза ниже, чем у современных флагманских смартфонов, но и ее хватает примерно на неделю в режиме ожидания (потребление около 6 мА) или на сутки активного пользования (потребление около 40 мА).
Настраиваем UART
Сегодня существует масса вариантов для программирования микроконтроллеров. Это и различные языки (С/С++, Rust), и самые разнообразные прикладные библиотеки, абстрагирующие разработку от аппаратного уровня (HAL от ST Microelectronics, Arduino Core и другие). Я использовал в проекте каноничный C и открытую libopencm3.
Первым делом следует инициализировать UART1, ведь именно он отвечает за общение с радиомодулем. Параметры стандартные: 115 200 бод и 8N1.
static void usart1_setup(void){ /* Enable clocks for GPIO port A (for GPIO_USART1_TX) and USART1 */ rcc_periph_clock_enable(RCC_GPIOA); rcc_periph_clock_enable(RCC_USART1); /* Enable the USART1 interrupt */ nvic_enable_irq(NVIC_USART1_IRQ); /* PA9 TX,PA10 RX */ gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO_USART1_TX); gpio_set_mode(GPIOA, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_USART1_RX); /* Setup UART parameters */ usart_set_baudrate(USART1, 115200); usart_set_databits(USART1, 8); usart_set_stopbits(USART1, USART_STOPBITS_1); usart_set_mode(USART1, USART_MODE_TX_RX); usart_set_parity(USART1, USART_PARITY_NONE); usart_set_flow_control(USART1, USART_FLOWCONTROL_NONE); usart_enable_rx_interrupt(USART1); usart_enable(USART1); }
После этого логично как-то организовать отправку команд на модуль. Например, c помощью сторонней реализации printf()
. Для этого используется библиотека rprintf. Ее код хорошо оптимизирован и занимает всего несколько килобайтов памяти. Библиотеку следует подправить для работы с libopencm3
, буквально несколько строк.
#38 #define UART USART1 ... #95 vfprintf_((&usart_send_blocking), format, arg); ... #142 ch = usart_recv_blocking(UART);
Теперь модулю можно отправлять команды вида printf_("AT_command")
, а ответ модуля принимается с использованием прерываний и сохраняется в буфер. После приема содержимое анализируется, и если это ожидаемый ответ, то вызывается функция-обработчик, которая используется для вывода сообщений SMS и USSD. Также возможен непосредственный вывод сообщения на экран, что очень удобно при отладке.
Работа с экраном
Как и любую другую периферию, дисплей перед использованием предстоит инициализировать. Конечно, сегодня подходящий код можно найти и в интернете, но я решил написать реализацию самостоятельно. Это не отнимет много времени, зато позволит лучше узнать возможности микросхемы ST7735. Я ориентировался на документацию (PDF) производителя и брал за основу готовые примеры в псевдокоде.
static void spi1_setup(void){ /* Enable SPI1 Periph and gpio clocks */ rcc_periph_clock_enable(RCC_SPI1); rcc_periph_clock_enable(RCC_GPIOA); /* Configure GPIOs: * SCK = PA5 * DC = PA6 * MOSI = PA7 * CS = PA1 * RST = PA4 * LED = PB0 */ gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO5 | GPIO7); gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO4 | GPIO6 | GPIO1); /* Reset SPI, SPI_CR1 register cleared, SPI is disabled */ spi_reset(SPI1); /* Set up SPI in Master mode with: * Clock baud rate: 1/64 of peripheral clock frequency * Clock polarity: Idle High * Clock phase: Data valid on 2nd clock pulse * Data frame format: 8-bit * Frame format: MSB First */ spi_init_master(SPI1, SPI_CR1_BAUDRATE_FPCLK_DIV_2, SPI_CR1_CPOL_CLK_TO_0_WHEN_IDLE, SPI_CR1_CPHA_CLK_TRANSITION_1, SPI_CR1_DFF_8BIT, SPI_CR1_MSBFIRST); /* Set NSS management to software */ spi_enable_software_slave_management(SPI1); spi_set_nss_high(SPI1); /* Enable SPI1 periph. */ spi_enable(SPI1); gpio_set(GPIOA, GPIO1); }
В библиотеке последовательно реализованы функции отрисовки точек, линий, окружностей, печати символов и целых строк и обновления экрана. Также поддерживается вывод символов кириллицы в кодировке CP866. Ключевой компонент кода — вызов st7735_sendchar(char* c)
, который позволяет последовательно выводить на экран строки, в том числе и с управляющими последовательностями. На данный момент поддерживаются символы перевода строки (\n
), возврата каретки (\r
), очистки экрана (\a
) и бэкспейса (\b
).
void st7735_sendchar(char ch){ uint16_t px, py; gpio_clear(STPORT, STCS); if (ch == '\r') { pos -= pos % 26; return; } if (ch == '\n') { pos += 26; pos -= pos % 26; return; } if (ch == '\a') { pos = 0; st7735_clear(bg_color); return; } if (ch == '\b') { pos--; px = (pos % 26) * 6; py = (pos / 26) * 8; st7735_drawchar(px, py, 0x20, txt_color, bg_color); return; } if(pos > 416) { pos=0; st7735_clear(bg_color); } px = (pos % 26) * 6; py = (pos /26) * 8; st7735_drawchar(px, py, ch, txt_color, bg_color); pos++; while (SPI_SR(SPI) & SPI_SR_BSY); gpio_set(STPORT,STCS); }
По умолчанию используется зеленый текст на черном фоне. Цвета можно указать явно, с помощью вызова функции st7735_set_printf_color(unit16_t text, uint16_t back)
. Кроме того, реализована дополнительная функция вывода текущего символа, который пользователь набирает на клавиатуре.
void st7735_virt_sendchar(char ch){ uint16_t px, py; gpio_clear(STPORT, STCS); px = (pos % 26) * 6; py = (pos / 26) * 8; if (ch > 0x20) { st7735_drawchar(px, py, ch, RED, bg_color); } while (SPI_SR(SPI) & SPI_SR_BSY); gpio_set(STPORT, STCS); }
Она похожа на st7735_sendchar()
, но не обрабатывает управляющие последовательности и не меняет текущую позицию символа. Таким образом, вызов функции st7735_sendchar()
после st7735_virt_sendchar()
перерисует на экране символ, выведенный st7735_virt_sendchar()
.
Клавиатура
Весь набор кнопок подключен через сдвиговые регистры к программному SPI. Обрабатывает пользовательский ввод библиотека 4х4key
. У клавиатуры две раскладки — русская и английская, в каждой раскладке за кнопкой закреплено по четыре символа.
Тут я несколько отступил от классики кнопочных телефонов нулевых годов — выбор конкретного символа определяется не количеством кликов, а длительностью нажатия. Это связано с тем, что в мобильниках обычно использовались мембранные клавиатуры, а тактовые кнопки тугие и перебирать буквы уже не так удобно.
Рассмотрим процесс обработки ввода подробнее. За опрос клавиатуры отвечает функция get_key()
. Для этого используется процедура read_key()
, которая считывает текущее состояние сдвиговых регистров и возвращает два байта информации от кнопок. Сочетаний клавиш в данный момент нет, но их можно легко добавить при необходимости.
Раскладка переключается при получении кода 0x0002
, в любом другом случае возвращается код символа. В зависимости от выбранного языка значение переменной ch_map
инкрементируется.
... while (!key_code) { key_code = read_key(); } do { if(key_code == 0x0002) { if (ch_map < 2) { ch_map++; else { ch_map = 0; } show_char_map(ch_map); while(key_code==2) key_code=read_key(); } while(!key_code) { key_code = read_key(); } } while (key_code == 0x0002); ...
Теперь вызывается функция key_map()
, которая принимает на вход код клавиши и номер текущей раскладки. Она ищет нужный символ в массиве char_map
и возвращает результат. Логика дальнейшей обработки ввода зависит от полученного символа.
... if (key == '\n') { delay(500); } else if (key == '\b') { delay(500); } else if(key == ' ') { #ifdef ECHO st7735_virt_sendchar(key); #endif delay(800); timer_start(); old_keycode = key_code; do { key_code = read_key(); if (key_code){ if (key_code == old_keycode) { count++; if (count > 15) { count = 0; } wait_key_counter = 0; } else { count = 0; break; } key = keymap2(count, 2); #ifdef ECHO systick_interrupt_disable(); st7735_virt_sendchar(key); systick_interrupt_enable(); #endif delay(900); } } while (wait_key_counter < 1000); timer_stop(); ...
Обработчик клавиатуры вышел сложнее, чем мне бы хотелось, но зато я получил практически все необходимые символы на шестнадцати клавишах, что довольно удобно. Однако в некоторых случаях хотелось бы мгновенной реакции на нажатие кнопок: например, когда вызываются пункты меню или принимается входящий вызов. Для этих целей реализована отдельная функция fast_get_key()
, которая работает с усеченным массивом символов.
char fast_get_key() { uint16_t key_code; char key; while (!key_code) { key_code=read_key(); } key = keymap(key_code, 0); while (read_key()) { __asm__("nop"); } #ifdef ECHO echo(key); #endif return key; }
Теперь, когда есть дисплей и клавиатура, от создания терминала нас отделяют только функции stprintf()
и kscanf()
. Они были реализованы с помощью уже упоминавшейся библиотеки rprintf
, но изменений тут потребовалось чуть больше.
int stprintf(const char \*format, …) { va_list arg; va_start(arg, format); stprintf_((&st7735_sendchar), format, arg); va_end(arg); return 0; }
С функцией kscanf()
все несколько сложнее, так как у нас есть две функции получения символа с клавиатуры. Поэтому придется объединить их в одну, организовав переключение ввода между get_key()
и fast_get_key()
. Заодно добавим поддержку управляющего символа \b
.
void set_scanf_mode(unsigned char mode) { fast_mode=mode; } int kscanf(const char* format, …) { va_list args; va_start( args, format ); int count = 0; char ch = 0; char buffer[kscanf_buff_size]; kscanf_buffer = buffer; while (count <= kscanf_buff_size ) { if(fast_mode) { ch = fast_get_key(); } else { ch = get_key(); } if (ch == '\b') { if (kscanf_buffer > buffer) { kscanf_buffer--; } continue; } else { count++; } if (ch != '\n' && ch != '\r') { *kscanf_buffer++ = ch; else { break; } } *kscanf_buffer = '\0'; kscanf_buffer = buffer; count = ksscanf(kscanf_buffer, format, args); va_end(args); return count; }
Таким образом мы реализовали систему ввода-вывода и теперь имеем практически полнофункциональный терминал. Например, чтобы очистить экран и вывести традиционное приветствие, достаточно написать строчку
stprintf("\aHello World!");
Модуль GSM
Рассмотрим работу с SIM800 на примере с отправкой SMS, остальные функции ведут себя аналогично. Будем использовать текстовый режим, так как он более наглядный. Дополнительно для отправки сообщений на кириллице нужно заранее настроить кодировку.
void sim800_init_cmd() { printf_("AT+CMGF=1\r\n"); for (uint32_t i = 0; i < 0xFFFFFF; i++) __asm__("nop"); printf_("AT+CSCS=\"UCS2\"\r\n"); for (uint32_t i = 0; i < 0xFFFFFF; i++) __asm__("nop"); printf_("AT+CSMP=17,167,0,8\r\n"); for (uint32_t i = 0; i < 0xFFFFFF; i++) __asm__("nop"); } void fast_sms_send(char *text, char *tel) { char *p_tel; char u_tel[64]="+7"; char temp[512]; if (tel[0] == '8') { p_tel = tel + 1; } else if (tel[0] == '+') { p_tel = tel + 2; } else { p_tel = tel; } strcat(u_tel, p_tel); strcpy(temp, text); cp866_to_utc2(temp); cp866_to_utc2(u_tel); stprintf("\aSend sms\r\nAT+CMGS=\"%s\"\r\n%s\x1A", u_tel, temp); printf_("AT+CMGS=\"%s\"\r\n", u_tel); for (uint32_t i = 0; i < 0xFFFFFF; i++) __asm__("nop"); printf_("%s\x1A", temp); }
Теперь можно использовать в коде что-то осмысленное и понятное, например
fast_sms_send("Hello world!", "89162402484");
Попробуем передать строку на кириллице, предварительно включив нужную кодировку:
void write_sms() { char text[256]; char tel[13]; uint8_t ret_code; stprintf("\aSMS writer v0.01\r\n" "Enter the sms text\r\n" ">"); kscanf("%s", text); ret_code = telbook_get_number(tel); if(!ret_code) { return; } fast_sms_send(text, tel); }
К слову, здесь в качестве номера контакта используется запись из телефонной книги. Думаю, о ней стоит рассказать чуть подробнее.
Телефонная книга
Как я уже говорил, информация о контактах хранится в микросхеме внешней памяти. Каждая запись занимает 32 байта: шестнадцать на номер телефона и столько же на имя абонента. Сейчас эти данные у меня записываются открытым текстом, без шифрования. Конечно, желательно использовать здесь AES или любой другой блочный шифр.
Основные возможности телефонной книги позволяют выбрать нужный номер контакта (telbook_get_number()
), а также добавить или удалить уже существующий (telbook_rec_add()
и telbook_rec_del()
). Кроме того, можно произвести поиск имени по телефону с помощью функции telbook_find_name()
. Для низкоуровневого взаимодействия с микросхемой памяти написана библиотека 25q32, которая берет на себя все нюансы аппаратной реализации.
Работа с графикой
Что можно еще попробовать, располагая цветным дисплеем и несколькими мегабайтами свободной памяти? Ну конечно же, вывод изображений, это само просится! Мобилка легко переваривает файлы ВМР с разрешением 128 на 160 и глубиной цвета в 16 бит. Картинки хранятся во внешней микросхеме и отображаются на экран с помощью функции img_from_flash()
, которая принимает адрес начала массива пикселей. Структура формата очень простая, но, если ты ее забыл, всегда можно почитать про заголовок и смещение в интернете.
Изображение на экран выводится частями с использованием буфера на стеке. В каждом проходе 4096 байт считываются из памяти в буфер, а потом пересылаются на экран. Конечно, можно заметить, что у F103C8T6 есть контроллер DMA, который предназначен именно для таких задач. Но, так как у нас нет возможности статически разместить весь буфер кадра в памяти, выигрыш от использования DMA здесь будет минимальным.
void img_from_flash_v3(uint32_t addr) { uint8_t bufer[4096]; gpio_clear(STPORT, STCS); st7735_sendcmd(ST7735_MADCTL); st7735_senddata(1 << 7); while (SPI_SR(SPI) & SPI_SR_BSY); gpio_set(GPIOA, STCS); for(uint8_t i = 0; i < 10; i++) { w25_read(addr + (i * 4096), bufer, 4096); st7735_drawimg(0,0+16*i,128,16, bufer); } gpio_clear(STPORT, STCS); st7735_sendcmd(ST7735_MADCTL); st7735_senddata(MV | MX); while (SPI_SR(SPI) & SPI_SR_BSY); gpio_set(STPORT, STCS); }
Разумеется, перед тем как обращаться к изображениям в памяти, предварительно их следует туда все же записать. Для этого используется UART2 и протокол xmodem. На принимающей стороне я обрабатываю данные функцией xmodem_to_flash()
, которой передается адрес начала файла во флеше.
void xmodem_to_flash(uint32_t addr) { unsigned char buf[132]; uint32_t byte = 0; uint8_t lastlen, ch; usart2_init(); usart_send_blocking(USARTX, NAK); while(1){ ch = usart_recv_blocking(USARTX); if (ch == SOH){ for (uint8_t i = 0; i < 131; i++) { ch = usart_recv_blocking(USARTX); buf[i]=ch; } lastlen=129; while(buf[lastlen--] == EOF); lastlen -= 1; w25_write(addr + byte, buf + 2, lastlen); byte += lastlen; usart_send_blocking(USARTX,ACK); continue; } if (ch == EOT){ usart_send_blocking(USARTX, ACK); break; } } usart2_deinit(); }
Таким образом, для записи файла с компьютера я начинаю передачу с помощью терминальной программы (например, minicom), после чего любым удобным способом вызываю функцию xmodem_to_flash()
.
Энергосбережение
Малое время автономной работы — слабое место современных смартфонов, в том числе даже флагманских аппаратов. В своем проекте я использовал несколько способов сокращения энергопотребления.
В первую очередь посадим на сухой паек радиомодуль. Команда AT+CSCLK=1
и высокий уровень на выводе DTR переводят SIM800C в спящий режим (sim800_sleep()
). При этом все еще сохраняется возможность принимать входящие вызовы и SMS, но для передачи команд с микроконтроллера нужно вновь подать низкий уровень на DTR и выждать порядка 50 мс (sim800_wake()
). В таком режиме потребление составляет всего несколько миллиамперов.
Подсветка дисплея тоже потребляет немало, так что на время ожидания логично отключать и ее (функции st7735_sleep()
и st7735_wake()
). Однако основной выигрыш электроэнергии получается за счет перевода микроконтроллера в режим глубокого сна, это позволяет сэкономить дополнительные 30 мА.
void standby (void) { SCB_SCR |= SCB_SCR_SLEEPDEEP; PWR_CR |= PWR_CR_PDDS; PWR_CR |= PWR_CR_CWUF; __asm__("WFI"); }
Заключительная строчка кода (Wait For Interrupt) переводит F103C8T6 в режим ожидания, из которого он выходит, только когда наступает прерывание. В нашем случае это подача низкого уровня на вывод REST микроконтроллера.
Интерфейс
Интерфейс устройства текстовый и реализован достаточно просто. При вызове соответствующего меню очищается экран и появляется подсказка с функциями клавиш. После этого ожидается пользовательский ввод, и цикл повторяется. Все функции меню собраны в отдельном файле menu.c
.
void main_help_menu(void) { stprintf("\aHELP\r\n" "~ - ATA\r\n" "! - ATH\r\n" "1 - data menu\r\n" "2 - call menu \r\n" "3 - img menu\r\n" "4 - power menu\r\n" "5 - sim800 menu\r\n" "6 - help\r\n" "7 - sim800 PWR\r\n" "8 - sleep\r\n" "9 - sleep logo\r\n" "* - tel book\r\n" "0 - sms menu"); } void get_keybord_cmd(void) { char bufer[64]; uint32_t addr, l, n = 4096; char key; key=fast_get_key(); switch (key){ case '1': data_menu(); break; case '2': telbook_menu_v2(); break; case '3': img_menu(); break; case '4': power_menu(); break; case '5': sim800_menu(); break; case '6': main_help_menu(); break; case '7': sim800_power(); break; case '8': st7735_sleep(); w25_powerdown(); standby(); break; case '9': w25_powerdown(); standby(); break; case '0': sms_menu(); break; case '*': telbook_menu(); break; case '~': sim800_take_call(); break; case '!': sim800_ath(); break; } return; }
Кратко расскажу о некоторых функциях главного меню. Команда ATA позволяет принять входящий вызов, команда ATH отклоняет вызов или завершает звонок. Меню data облегчает работу с внешней памятью и дает возможность в реальном времени посмотреть дамп любого участка, как в ASCII, так и в HEX. Также здесь можно перезаписать байты по произвольным адресам, вплоть до ручного управления полями в контактах (хотя конкретно для этого удобнее воспользоваться подходящим разделом).
Меню call служит для быстрого набора номера из телефонной книги, тогда как tel book позволяет редактировать и добавлять записи новых абонентов. Power menu управляет настройками энергосбережения, а команды sleep и sleep logo переводят устройство в режим сна (примерно 6 и 9 мА потребления соответственно).
Есть и несколько дополнительных полезностей. Меню img выполняет функции галереи и предоставляет доступ к сохраненным картинкам, а sim800 напрямую взаимодействует с радиомодулем через стандартные команды АТ. Как будто не самая очевидная вещь, но она мне здорово пригодилась при отладке.
Идеи и развитие проекта
Я получил большое удовольствие от работы над своей мобилкой, последовательно воплощая в жизнь разные функции, отлавливая ошибки и решая возникающие в процессе задачи. И само собой, останавливаться на достигнутом я вовсе не намерен. Вот несколько идей, которые я пока не реализовал, но уже запланировал на ближайшее будущее: встроенные игры, шифрование данных, отправка и прием MMS, записная книжка, дополнительные поля в контактах.
И это далеко не полный список. Современные микроконтроллеры имеют массу полезных интерфейсов и позволяют подключать самую разную периферию: микросхемы внешней оперативной памяти, карточки SD, экраны с высоким разрешением и даже цифровые камеры. Кажется, так можно увлечься и собрать полноценный смартфон!