Hacker
Professional
- Messages
- 1,044
- Reaction score
- 834
- Points
- 113
The content of the article
It all started with the fact that some time ago I was assembling a phone for myself on a GSM communication module. The modern electronics were housed in a vintage case with an outer tube and a rotary dial (do you still remember how to use them?). Alas, because of the ill-conceived scheme, it was inconvenient to call from him.
Then I decided to make a second attempt, but with a new concept. I wanted to create a compact device that performs the functions of a simple push-button telephone and at the same time is suitable for practical use. Ideally, even a small battery should last for at least a week. No unnecessary services, suspicious applications and annoying notifications, only the most necessary things - calls, SMS and phone book.
The project was shown at Chaos Constructions 2021 and, to my (pleasant) surprise, aroused interest from a wide audience. Many were curious to know the internal structure of a mobile phone, so today I will tell you in detail how you can assemble such a gadget yourself.
Component block diagram
First, let's define the requirements for the device: we need to make outgoing calls, receive incoming calls, read and write SMS (including in Cyrillic) and manage contacts in the phone book. This is the basic functionality that users have come to expect from push-button phones. Of course, this is not a complete list and there is a lack of at least built-in games (snake or Tetris), but it will be easy to add them already at the final stage.
The key component of the device will be the SIM800C cellular communication module. It contains a complete radio path, an audio path and implements the basic functions of working with the GSM network. In other words, this is an almost ready-made GSM-UART bridge, which only needs to be controlled via an external terminal.
To do this, we need a screen, keyboard and some kind of microcontroller to execute the main program. As a screen, I used an ST7735 display module with a resolution of 128 by 160 pixels. I already had a ready-made library for it, which allowed me to draw symbols and graphic primitives. By and large, the choice of display is not critical for the project, and you can use any other with a suitable diagonal.
The keyboard with sixteen buttons is implemented on shift registers (a pair of eight-bit 74HC165 microcircuits (PDF). You can also use their domestic analogue - integrated circuits of the company "Integral" KR1533IR9. In any case, the output of such registers is an inferior SPI, since even when turned off they are not go into a high impedance state. Therefore, instead of hardware and combined with the SPI bus display software implementation used for them.
Everything will be controlled by a microcontroller of the STM32 family. Since special performance is not required, even budget solutions will do. I opted for F103C8T6 (PDF), its resources should be enough here in abundance. In addition, it is on such a microcontroller that the well-known model line of BluePill debug boards is produced (an excellent tool for getting rid of Arduino dependency). This made it possible to assemble a prototype and test the operation of the components almost from the very start.
Later (and as a nice bonus) I decided to add a 32 Mbit external read-only memory W25Q32 (PDF) to the project. This made it possible not to overwrite the flash of the microcontroller itself and to store all contacts separately. In addition, it became possible to download pictures, symbols and other elements of raster graphics to the phone.
The circuit of a mobile phone itself is quite standard and hardly needs any comments. SIM800C turns on when a low level is applied to the REST pin (using the Q1 transistor connected to the PA0 pin of the microcontroller). Additionally, LEDs VD2 and VD3 indicate the status of the radio module. VD2 blinks on successful connection, while VD3 is on for as long as SIM800C is active.
The components are housed on two single-sided printed circuit boards, predominantly surface-mounted. The first board contains a radio module, a microcontroller, an external memory chip, and connectors for an antenna and a speaker. The second board is entirely dedicated to the keyboard. The assembled structure is placed in a plexiglass case and fixed on the M3 racks.
Our device is powered by a lithium-polymer battery of 1500 mA consumption about 40 mA).
Configuring the UART
Today there are many options for programming microcontrollers. These are various languages (С / С ++, Rust), and a wide variety of application libraries that abstract development from the hardware level (HAL from ST Microelectronics, Arduino Core and others). I used canonical C and open source libopencm3 in the project.
The first step is to initialize UART1, because it is he who is responsible for communicating with the radio module. The parameters are standard: 115,200 baud and 8N1.
After that, it is logical to somehow organize the sending of commands to the module. For example, using a third-party implementation printf(). The rprintf library is used for this. Its code is well optimized and takes only a few kilobytes of memory. The library needs to be tweaked to work with libopencm3, literally a few lines.
Now commands of the form can be sent to the module printf_("AT_command"), and the module's response is received using interrupts and stored in a buffer. After receiving, the content is analyzed, and if this is the expected response, then the handler function is called, which is used to display SMS and USSD messages. It is also possible to directly display the message on the screen, which is very convenient for debugging.
Working with the screen
Like any other peripheral, the display must be initialized before use. Of course, today you can find suitable code on the Internet, but I decided to write the implementation myself. It will not take much time, but it will allow you to better know the capabilities of the ST7735 microcircuit. I was guided by the manufacturer's documentation (PDF) and took ready-made examples in pseudocode as a basis .
The library consistently implements the functions of drawing points, lines, circles, printing symbols and entire lines, and updating the screen. The output of Cyrillic characters in CP866 encoding is also supported . The key component of the code is a call st7735_sendchar(char* c)that allows you to sequentially display lines, including those with escape sequences. Currently supported characters are line feed ( \n), carriage return ( \r), screen clear ( \a) and backspace ( \b).
The default is green text on a black background. Colors can be specified explicitly using a function call st7735_set_printf_color(unit16_t text, uint16_t back). In addition, an additional function is implemented to display the current character that the user types on the keyboard.
It is similar to st7735_sendchar()but does not process escape sequences and does not change the current character position. Thus, the call to the function st7735_sendchar()after will st7735_virt_sendchar()redraw the character displayed on the screen st7735_virt_sendchar().
Keyboard
The entire set of buttons is connected via shift registers to the software SPI. The library handles user input 4х4key. The keyboard has two layouts - Russian and English, in each layout four characters are assigned to the button.
Here I deviated somewhat from the classic push-button telephones of the 2000s - the choice of a particular symbol is determined not by the number of clicks, but by the duration of pressing. This is due to the fact that in mobile phones membrane keyboards were usually used, and the tact buttons are tight and it is no longer so convenient to go through the letters.
Let's take a closer look at the input processing. The function is responsible for polling the keyboard get_key(). For this, a procedure is used read_key()that reads the current state of the shift registers and returns two bytes of information from the buttons. There are no keyboard shortcuts at the moment, but you can easily add them as needed.
The layout switches when a code is received 0x0002, in any other case a character code is returned. The value of the variable is ch_mapincremented depending on the selected language .
Now the function is called key_map(), which takes as input the key code and the number of the current layout. It searches the array for the desired character char_mapand returns the result. The logic for further processing the input depends on the received character.
The keyboard handler came out more complicated than I would like, but I got almost all the necessary characters on sixteen keys, which is quite convenient. However, in some cases, I would like an instant reaction to pressing buttons: for example, when menu items are called up or an incoming call is received. For these purposes, a separate function fast_get_key()has been implemented that works with a truncated character array.
Now that we have a display and a keyboard, only the stprintf()and functions separate us from the creation of the terminal kscanf(). They were implemented using the already mentioned library rprintf, but a little more changes were required here.
With the function, kscanf()everything is somewhat more complicated, since we have two functions for receiving a character from the keyboard. Therefore, you will have to combine them into one, organizing the input switching between get_key()and fast_get_key(). At the same time, we will add support for the control character \b.
Thus, we have implemented an input-output system and now we have an almost fully functional terminal. For example, to clear the screen and display the traditional greeting, just write the line
GSM module
Let's consider working with SIM800 using an example of sending SMS, other functions behave in the same way. We will use text mode as it is more descriptive. Additionally, to send messages in Cyrillic, you need to configure the encoding in advance.
Now you can use something meaningful and understandable in your code, for example
By the way, here an entry from the phone book is used as a contact number. I think it is worth talking about it in a little more detail.
Phone book
As I said before, contact information is stored in an external memory chip. Each entry takes 32 bytes: sixteen for the phone number and the same for the subscriber's name. Now this data is recorded in clear text, without encryption. Of course, it is advisable to use AES or any other block cipher here.
Key features phonebook allow you to select the desired contact number ( telbook_get_number()), as well as add or remove an existing ( telbook_rec_add()and telbook_rec_del()). Alternatively, you can search for a name by phone using the function telbook_find_name(). For low-level interaction with the memory microcircuit, the 25q32 library has been written, which takes care of all the nuances of the hardware implementation.
Working with graphics
What else can you try with a color display and a few megabytes of free memory? Well, of course, the output of images is asking for itself! The mobile phone easily digests BMP files with a resolution of 128 by 160 and a color depth of 16 bits. Pictures are stored in an external microcircuit and displayed on the screen using a function img_from_flash()that takes the address of the beginning of the pixel array. The structure of the format is very simple, but if you've forgotten it, you can always read about the title and offset on the Internet.
The image is displayed on the screen in chunks using a buffer on the stack. In each pass, 4096 bytes are read from memory into a buffer, and then sent to the screen. Of course, you will notice that the F103C8T6 has a DMA controller that is specifically designed for such tasks. But, since we do not have the opportunity to statically allocate the entire frame buffer in memory, the gain from using DMA will be minimal here.
Of course, before accessing the images in memory, they should still be written there beforehand. For this, UART2 and the xmodem protocol are used. On the receiving side, I process the data with a function xmodem_to_flash()that is passed the address of the beginning of the file in the flash.
Thus, to write a file from a computer, I start the transfer using a terminal program (for example, minicom), after which I call the function in any convenient way xmodem_to_flash().
Energy saving
Low battery life is a weak point of modern smartphones, including even flagship devices. In my project, I used several methods to reduce energy consumption.
First of all, let's put the radio module on dry rations. The command AT+CSCLK=1and a high level on the DTR pin put the SIM800C into sleep mode ( sim800_sleep()). At the same time, it is still possible to receive incoming calls and SMS, but to transmit commands from the microcontroller, you need to send a low level to DTR again and wait about 50 ms ( sim800_wake()). In this mode, consumption is only a few milliamps.
The display backlight also consumes a lot, so it is logical to turn it off ( st7735_sleep()and functions st7735_wake()) while waiting . However, the main power gain comes from putting the microcontroller into deep sleep mode, which saves an additional 30 mA.
The final line of code (Wait For Interrupt) puts F103C8T6 into standby mode, from which it exits only when an interrupt occurs. In our case, this is a low-level feed to the REST pin of the microcontroller.
Interface
The device interface is textual and implemented quite simply. When the corresponding menu is called up, the screen is cleared and a tooltip with key functions appears. After that, user input is expected and the loop repeats. All menu functions are collected in a separate file menu.c.
I will briefly talk about some of the functions of the main menu. The ATA command accepts an incoming call, the ATH command rejects the call or hangs up the call. The data menu makes it easier to work with external memory and makes it possible to view a dump of any area in real time, both in ASCII and in HEX. Also, here you can overwrite bytes at arbitrary addresses, up to manual control of fields in contacts (although it is more convenient to use the appropriate section for this specifically).
The call menu is used to quickly dial a number from the phone book, while tel book allows you to edit and add records of new subscribers. The Power menu controls the power saving settings, and the sleep and sleep logo commands put the device into sleep mode (approximately 6 and 9 mA consumption, respectively).
There are also a few extras. The img menu acts as a gallery and provides access to saved pictures, and the sim800 directly interacts with the radio module through standard AT commands. As if not the most obvious thing, but it came in handy for me when debugging.
Ideas and project development
I got a lot of pleasure from working on my mobile phone, consistently implementing different functions, catching errors and solving problems that arise in the process. And of course, I do not intend to stop there. Here are a few ideas that I have not yet implemented, but have already planned for the near future: built-in games, data encryption, sending and receiving MMS, notebook, additional fields in contacts.
And this is not a complete list. Modern microcontrollers have a lot of useful interfaces and allow you to connect a wide variety of peripherals: external RAM chips, SD cards, high-resolution screens and even digital cameras. It seems that you can get carried away and assemble a full-fledged smartphone!
- Component block diagram
- Configuring the UART
- Working with the screen
- Keyboard
- GSM module
- Phone book
- Working with graphics
- Energy saving
- Interface
- Ideas and project development
It all started with the fact that some time ago I was assembling a phone for myself on a GSM communication module. The modern electronics were housed in a vintage case with an outer tube and a rotary dial (do you still remember how to use them?). Alas, because of the ill-conceived scheme, it was inconvenient to call from him.
Then I decided to make a second attempt, but with a new concept. I wanted to create a compact device that performs the functions of a simple push-button telephone and at the same time is suitable for practical use. Ideally, even a small battery should last for at least a week. No unnecessary services, suspicious applications and annoying notifications, only the most necessary things - calls, SMS and phone book.
The project was shown at Chaos Constructions 2021 and, to my (pleasant) surprise, aroused interest from a wide audience. Many were curious to know the internal structure of a mobile phone, so today I will tell you in detail how you can assemble such a gadget yourself.
Component block diagram
First, let's define the requirements for the device: we need to make outgoing calls, receive incoming calls, read and write SMS (including in Cyrillic) and manage contacts in the phone book. This is the basic functionality that users have come to expect from push-button phones. Of course, this is not a complete list and there is a lack of at least built-in games (snake or Tetris), but it will be easy to add them already at the final stage.
The key component of the device will be the SIM800C cellular communication module. It contains a complete radio path, an audio path and implements the basic functions of working with the GSM network. In other words, this is an almost ready-made GSM-UART bridge, which only needs to be controlled via an external terminal.
To do this, we need a screen, keyboard and some kind of microcontroller to execute the main program. As a screen, I used an ST7735 display module with a resolution of 128 by 160 pixels. I already had a ready-made library for it, which allowed me to draw symbols and graphic primitives. By and large, the choice of display is not critical for the project, and you can use any other with a suitable diagonal.
The keyboard with sixteen buttons is implemented on shift registers (a pair of eight-bit 74HC165 microcircuits (PDF). You can also use their domestic analogue - integrated circuits of the company "Integral" KR1533IR9. In any case, the output of such registers is an inferior SPI, since even when turned off they are not go into a high impedance state. Therefore, instead of hardware and combined with the SPI bus display software implementation used for them.
Everything will be controlled by a microcontroller of the STM32 family. Since special performance is not required, even budget solutions will do. I opted for F103C8T6 (PDF), its resources should be enough here in abundance. In addition, it is on such a microcontroller that the well-known model line of BluePill debug boards is produced (an excellent tool for getting rid of Arduino dependency). This made it possible to assemble a prototype and test the operation of the components almost from the very start.
Later (and as a nice bonus) I decided to add a 32 Mbit external read-only memory W25Q32 (PDF) to the project. This made it possible not to overwrite the flash of the microcontroller itself and to store all contacts separately. In addition, it became possible to download pictures, symbols and other elements of raster graphics to the phone.
The circuit of a mobile phone itself is quite standard and hardly needs any comments. SIM800C turns on when a low level is applied to the REST pin (using the Q1 transistor connected to the PA0 pin of the microcontroller). Additionally, LEDs VD2 and VD3 indicate the status of the radio module. VD2 blinks on successful connection, while VD3 is on for as long as SIM800C is active.
The components are housed on two single-sided printed circuit boards, predominantly surface-mounted. The first board contains a radio module, a microcontroller, an external memory chip, and connectors for an antenna and a speaker. The second board is entirely dedicated to the keyboard. The assembled structure is placed in a plexiglass case and fixed on the M3 racks.
Our device is powered by a lithium-polymer battery of 1500 mA consumption about 40 mA).
Configuring the UART
Today there are many options for programming microcontrollers. These are various languages (С / С ++, Rust), and a wide variety of application libraries that abstract development from the hardware level (HAL from ST Microelectronics, Arduino Core and others). I used canonical C and open source libopencm3 in the project.
The first step is to initialize UART1, because it is he who is responsible for communicating with the radio module. The parameters are standard: 115,200 baud and 8N1.
Code:
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);
}
After that, it is logical to somehow organize the sending of commands to the module. For example, using a third-party implementation printf(). The rprintf library is used for this. Its code is well optimized and takes only a few kilobytes of memory. The library needs to be tweaked to work with libopencm3, literally a few lines.
Code:
# 38 #define UART USART1
...
# 95 vfprintf _ ((& usart_send_blocking), format, arg);
...
# 142 ch = usart_recv_blocking (UART);
Now commands of the form can be sent to the module printf_("AT_command"), and the module's response is received using interrupts and stored in a buffer. After receiving, the content is analyzed, and if this is the expected response, then the handler function is called, which is used to display SMS and USSD messages. It is also possible to directly display the message on the screen, which is very convenient for debugging.
Working with the screen
Like any other peripheral, the display must be initialized before use. Of course, today you can find suitable code on the Internet, but I decided to write the implementation myself. It will not take much time, but it will allow you to better know the capabilities of the ST7735 microcircuit. I was guided by the manufacturer's documentation (PDF) and took ready-made examples in pseudocode as a basis .
Code:
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_MS)
/ * 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);
}
The library consistently implements the functions of drawing points, lines, circles, printing symbols and entire lines, and updating the screen. The output of Cyrillic characters in CP866 encoding is also supported . The key component of the code is a call st7735_sendchar(char* c)that allows you to sequentially display lines, including those with escape sequences. Currently supported characters are line feed ( \n), carriage return ( \r), screen clear ( \a) and backspace ( \b).
Code:
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);
}
The default is green text on a black background. Colors can be specified explicitly using a function call st7735_set_printf_color(unit16_t text, uint16_t back). In addition, an additional function is implemented to display the current character that the user types on the keyboard.
Code:
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);
}
It is similar to st7735_sendchar()but does not process escape sequences and does not change the current character position. Thus, the call to the function st7735_sendchar()after will st7735_virt_sendchar()redraw the character displayed on the screen st7735_virt_sendchar().
Keyboard
The entire set of buttons is connected via shift registers to the software SPI. The library handles user input 4х4key. The keyboard has two layouts - Russian and English, in each layout four characters are assigned to the button.
Here I deviated somewhat from the classic push-button telephones of the 2000s - the choice of a particular symbol is determined not by the number of clicks, but by the duration of pressing. This is due to the fact that in mobile phones membrane keyboards were usually used, and the tact buttons are tight and it is no longer so convenient to go through the letters.
Let's take a closer look at the input processing. The function is responsible for polling the keyboard get_key(). For this, a procedure is used read_key()that reads the current state of the shift registers and returns two bytes of information from the buttons. There are no keyboard shortcuts at the moment, but you can easily add them as needed.
The layout switches when a code is received 0x0002, in any other case a character code is returned. The value of the variable is ch_mapincremented depending on the selected language .
Code:
...
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);
...
Now the function is called key_map(), which takes as input the key code and the number of the current layout. It searches the array for the desired character char_mapand returns the result. The logic for further processing the input depends on the received character.
Code:
...
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 ();
...
The keyboard handler came out more complicated than I would like, but I got almost all the necessary characters on sixteen keys, which is quite convenient. However, in some cases, I would like an instant reaction to pressing buttons: for example, when menu items are called up or an incoming call is received. For these purposes, a separate function fast_get_key()has been implemented that works with a truncated character array.
Code:
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;
}
Now that we have a display and a keyboard, only the stprintf()and functions separate us from the creation of the terminal kscanf(). They were implemented using the already mentioned library rprintf, but a little more changes were required here.
Code:
int stprintf (const char \ * format, ...) {
va_list arg;
va_start (arg, format);
stprintf _ ((& st7735_sendchar), format, arg);
va_end (arg);
return 0;
}
With the function, kscanf()everything is somewhat more complicated, since we have two functions for receiving a character from the keyboard. Therefore, you will have to combine them into one, organizing the input switching between get_key()and fast_get_key(). At the same time, we will add support for the control character \b.
Code:
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;
}
Thus, we have implemented an input-output system and now we have an almost fully functional terminal. For example, to clear the screen and display the traditional greeting, just write the line
Code:
stprintf ("\ aHello World!");
GSM module
Let's consider working with SIM800 using an example of sending SMS, other functions behave in the same way. We will use text mode as it is more descriptive. Additionally, to send messages in Cyrillic, you need to configure the encoding in advance.
Code:
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);
}
Now you can use something meaningful and understandable in your code, for example
Code:
fast_sms_send ("Hello world!", "89162402484");
Let's try to transfer a string in Cyrillic, having previously enabled the required encoding:
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);
}
By the way, here an entry from the phone book is used as a contact number. I think it is worth talking about it in a little more detail.
Phone book
As I said before, contact information is stored in an external memory chip. Each entry takes 32 bytes: sixteen for the phone number and the same for the subscriber's name. Now this data is recorded in clear text, without encryption. Of course, it is advisable to use AES or any other block cipher here.
Key features phonebook allow you to select the desired contact number ( telbook_get_number()), as well as add or remove an existing ( telbook_rec_add()and telbook_rec_del()). Alternatively, you can search for a name by phone using the function telbook_find_name(). For low-level interaction with the memory microcircuit, the 25q32 library has been written, which takes care of all the nuances of the hardware implementation.
Working with graphics
What else can you try with a color display and a few megabytes of free memory? Well, of course, the output of images is asking for itself! The mobile phone easily digests BMP files with a resolution of 128 by 160 and a color depth of 16 bits. Pictures are stored in an external microcircuit and displayed on the screen using a function img_from_flash()that takes the address of the beginning of the pixel array. The structure of the format is very simple, but if you've forgotten it, you can always read about the title and offset on the Internet.
The image is displayed on the screen in chunks using a buffer on the stack. In each pass, 4096 bytes are read from memory into a buffer, and then sent to the screen. Of course, you will notice that the F103C8T6 has a DMA controller that is specifically designed for such tasks. But, since we do not have the opportunity to statically allocate the entire frame buffer in memory, the gain from using DMA will be minimal here.
Code:
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);
}
Of course, before accessing the images in memory, they should still be written there beforehand. For this, UART2 and the xmodem protocol are used. On the receiving side, I process the data with a function xmodem_to_flash()that is passed the address of the beginning of the file in the flash.
Code:
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 ();
}
Thus, to write a file from a computer, I start the transfer using a terminal program (for example, minicom), after which I call the function in any convenient way xmodem_to_flash().
Energy saving
Low battery life is a weak point of modern smartphones, including even flagship devices. In my project, I used several methods to reduce energy consumption.
First of all, let's put the radio module on dry rations. The command AT+CSCLK=1and a high level on the DTR pin put the SIM800C into sleep mode ( sim800_sleep()). At the same time, it is still possible to receive incoming calls and SMS, but to transmit commands from the microcontroller, you need to send a low level to DTR again and wait about 50 ms ( sim800_wake()). In this mode, consumption is only a few milliamps.
The display backlight also consumes a lot, so it is logical to turn it off ( st7735_sleep()and functions st7735_wake()) while waiting . However, the main power gain comes from putting the microcontroller into deep sleep mode, which saves an additional 30 mA.
Code:
void standby (void) {
SCB_SCR | = SCB_SCR_SLEEPDEEP;
PWR_CR | = PWR_CR_PDDS;
PWR_CR | = PWR_CR_CWUF;
__asm __ ("WFI");
}
The final line of code (Wait For Interrupt) puts F103C8T6 into standby mode, from which it exits only when an interrupt occurs. In our case, this is a low-level feed to the REST pin of the microcontroller.
Interface
The device interface is textual and implemented quite simply. When the corresponding menu is called up, the screen is cleared and a tooltip with key functions appears. After that, user input is expected and the loop repeats. All menu functions are collected in a separate file menu.c.
Code:
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;
}
I will briefly talk about some of the functions of the main menu. The ATA command accepts an incoming call, the ATH command rejects the call or hangs up the call. The data menu makes it easier to work with external memory and makes it possible to view a dump of any area in real time, both in ASCII and in HEX. Also, here you can overwrite bytes at arbitrary addresses, up to manual control of fields in contacts (although it is more convenient to use the appropriate section for this specifically).
The call menu is used to quickly dial a number from the phone book, while tel book allows you to edit and add records of new subscribers. The Power menu controls the power saving settings, and the sleep and sleep logo commands put the device into sleep mode (approximately 6 and 9 mA consumption, respectively).
There are also a few extras. The img menu acts as a gallery and provides access to saved pictures, and the sim800 directly interacts with the radio module through standard AT commands. As if not the most obvious thing, but it came in handy for me when debugging.
Ideas and project development
I got a lot of pleasure from working on my mobile phone, consistently implementing different functions, catching errors and solving problems that arise in the process. And of course, I do not intend to stop there. Here are a few ideas that I have not yet implemented, but have already planned for the near future: built-in games, data encryption, sending and receiving MMS, notebook, additional fields in contacts.
And this is not a complete list. Modern microcontrollers have a lot of useful interfaces and allow you to connect a wide variety of peripherals: external RAM chips, SD cards, high-resolution screens and even digital cameras. It seems that you can get carried away and assemble a full-fledged smartphone!