【YY手机】用AVR单片机制作手机系列教程-制作篇

来源:互联网 发布:chromium linux 64 编辑:程序博客网 时间:2024/04/27 20:26

本章节是制作教程,教你连接硬件,烧录程序,然后你就拥有一部YY手机了。

关于程序的原理,请参考我上传的源码,源码中有详细的注释(基础功能相关的程序请参考书籍,如USART等资源的使用)。后续章节中,也会有关于不常用模块的使用教程。


第一步·硬件连接

为了方便二次开发、扩展应用,YY手机硬件方案中预留出了SPI总线接口和I2C总线接口,方便读者连接其他传感器或单片机。

具体连接方式为:

·4x4矩阵键盘连接PORTA。

·1602的8位数据位的低四位连接PORTB的低四位,高四位连接PORTC的高四位。RS连接PD5,R/W连接PD6,E连接PD7。

·GPRS模块连接USART接口(PD0和PD1)。

·麦克风的信号口连接GPRS模块的MIC+接口。VCC和GND正常接。

 

第二步·刷上示例代码。具体方式参考我的博客,blog.csdn.net/daqianc/article/details/53199571。当然可以采用别的方式。

 

恭喜你,你现在应该有一个YY手机了。对着代码尽情玩耍吧。



 

(上面照片在制作过程中拍摄,与最终用户界面不同)


下面对代码做一些简单的说明。

 

首先,程序以模块化方式编写。每个模块都有各自对应的函数,以完成本模块的基本功能。

第二,主函数负责调动初始的功能选择操作,包括:打电话、发短信、发邮件、拍照片。在主函数循环中,串口中断打开,此时可以接电话。当进入某一功能的流程中后,串口中断关闭,不能接电话。这主要是为了逻辑简单而偷懒。

第三,在程序开始时有长约一分钟的初始化时间,是用来让GPRS模块注册并附着GPRS网络的。在信不不强的地区时长可能不够,尽管手机使用起来没有异常,但是功能会失效。

第四,GPRS模块命令处理需要时长,处理完之后通常会返回信息,例如“OK”等。例如在给附着GPRS网络的命令之后,会有大概五秒的等候时长。在本程序中为了简化,没有等待串口接收“OK”,而是等一个模糊的时长。这显然不是最好的,因为有的时候等候的时长超过了设置的值,这时如果用户进行了下一步操作,则两条指令都会失效,并且会对后面的操作产生影响;有兴趣的读者可以自己修改完善这方面的程序。


以下是完整代码

/* * 1602LCD.c *  说明: 所有const uchar table_xxx[] 的字符串常亮定义,都是用来显示的 所有uchar AT_xxx[] 的字符串定义,都是要通过串口发送给GSM模块的AT指令 仅在空闲(没有正在进行的任务)时可以接电话,正在执行其他任务时串口中断关闭,导致不可接电话      * Created: 2016/10/8 15:07:04 * Author : DaqianC */ #include <stdlib.h>#include <avr/io.h>#include <avr/interrupt.h>#define F_CPU 11059200UL#include <util/delay.h>#include <string.h>#define uchar unsigned char#define uint unsigned int//一些关于1602屏的操作宏定义 #define LCDRS_SET (PORTD |= 0x20)#define LCDRS_CLR (PORTD &= 0xdf)#define LCDRW_SET (PORTD |= 0x40)#define LCDRW_CLR (PORTD &= 0xbf)#define LCDEN_SET (PORTD |= 0x80)#define LCDEN_CLR (PORTD &= 0x7f)//三个全局数组长度的宏定义 #define LIST_NUM_LENGTH 20#define LIST_STR_LENGTH 150#define LIST_RC_LENGTH 70unsigned char array_num[LIST_NUM_LENGTH];//全局数组,用于存储数字输入模式下从键盘输入的数字串(ASCII码值) unsigned char array_str[LIST_STR_LENGTH];//全局数组,用于存储字符输入模式下从键盘输入的字符串 volatile unsigned char array_rc[LIST_RC_LENGTH];//作为串口接收缓冲池 volatile unsigned char array_rc_index = 0;//作为串口接收缓冲池的TOP const unsigned char table_Home[] = "    YY PHONE";//凡是const uchar=table_...的,都是用来显示的字符串 const unsigned char table_option[] = "   A  B  C  D";//1602显示屏相关函数声明 void lcd_write_cmd(uchar cmd);//向1602屏发送指令 unsigned char read_cmd(void);//读取1602的指令 void lcd_write_data(uchar inf);//向1602屏发送要显示的内容 void init_lcd(void);//初始化1602屏 void clear_display(void);//清空显示 void display_string(const unsigned char*);//显示一个字符串 void lcd_delete(void);//从光标处删除上一个字符 //USART相关函数声明 void USART_init(void);//初始化USART unsigned char receive_byte(void);//接收一个字符 void USART_transmit_byte(unsigned char data);//发送一个字符 void USART_transmit_str(unsigned char* str);//发送一个字符串 void USART_RX_Proc(void);//处理串口接收缓冲池中的字符串 //GSM模块相关函数声明 void GSM_init(void);//初始化GSM模块 void GSM_SMS(void);//发送短信 void GSM_call(void);//打电话 void GSM_RcCall(void);//接电话 void GSM_mail(void);//发邮件 void GSM_photo(void);//拍照片并发邮件 //键盘相关函数声明 unsigned char keyboard_val[16] = {'1','4','7','*','2','5','8','0','3','6','9','#','A','B','C','D'};//简单输入映射表,直接对应4x4键盘 unsigned char keyboard_9keysVal[70] = {'0', ' ', '(', ')', ',', '.', '!','1','a','b','c','A','B','C','2','d','e','f','D','E','F','3','g','h','i','G','H','I','4','j','k','l','J','K','L','5','m','n','o','M','N','O','6','p','q','r','P','Q','R','7','s','t','u','S','T','U','8','v','w','x','V','W','X','9','y','z','@','Y','Z','?'};//字符型输入映射表,每个按键有七个值,通过按的次数确定 void keyboard_init();//初始化矩阵键盘 unsigned char keyboard_scan();//标准键盘输入函数,扫描矩阵键盘,返回对应的16个字符中的一个unsigned char keyboard_9keys();//字符型键盘输入函数,一个按键对应7个可能的值,返回一个字符 void keyboard_inputNum();//键盘输入数字串,存储到全局字符数组array_num里 void keyboard_inputStr();//键盘输入字符串,存储到全局字符数组array_str里 int main(void)//主函数 {//以下三个函数是几个模块的初始化函数 init_lcd();keyboard_init();USART_init();const unsigned char table_init[] = "Init...";display_string(table_Home);lcd_write_cmd(0x80 + 0x40);//程序中所有lcd_write_cmd(0x80 + 0x40)都是将光标切换到1602屏第二行起始处 display_string(table_init);_delay_ms(25000);//等待25秒,等待GSM模块注册 GSM_init();//等待25秒后进行GSM模块的其他初始化操作,具体参见函数 lcd_write_cmd(0x80 + 0x40);display_string(table_option);    while (1)     {unsigned char menu = keyboard_scan();//扫描键盘 if(menu){SREG &= 0x7f;//关闭全局中断,防止在进行其他操作时进入接电话模式(虽然不科学,但是好调) switch(menu){case 'A':GSM_SMS();//若键盘按A,则进入发短信模式 break;case 'B':GSM_call();//若键盘按B,则进入打电话模式 break;case 'C':GSM_mail();//若键盘按C,则进入发邮件模式 break;case 'D':GSM_photo();//若键盘按D,则进入照相并发送邮件模式 break;}clear_display();//执行完各自的函数流程,回到主界面 display_string(table_Home);lcd_write_cmd(0x80 + 0x40);display_string(table_option);SREG |= 0x80;//开启全局中断,可以接电话 }USART_RX_Proc();//处理串口接收缓冲池中的数据 }return 0;}//****************GSM-related*************void GSM_init(void){unsigned char AT_CLIP[] = "AT+CLIP=1\r";//程序中所有以“AT_”开头的字符串,都是AT指令 unsigned char AT_CGATT[] = "AT+CGATT=1\r";//本指令与下一条指令用来附着GPRS网络 unsigned char AT_CGACT[] = "AT+CGACT=1,1\r";unsigned char AT_CAMSTART[] = "AT+CAMSTART=1\r";//初始化相机 const unsigned char table_net[] = "Init Net.";USART_transmit_str(AT_CLIP);_delay_ms(2000);lcd_write_cmd(0x80 + 0x40);display_string(table_net);USART_transmit_str(AT_CGATT);_delay_ms(20000);USART_transmit_str(AT_CGACT);_delay_ms(2000);USART_transmit_str(AT_CAMSTART);_delay_ms(2000);UCSRB |= 0x90;//开启串口接收的中断 SREG |= 0x80;}void GSM_photo(void){clear_display();unsigned char AT_CAMCAP[] = "AT+CAMCAP\r";//拍摄照片 unsigned char AT_CAMUPLOAD1[] = "AT+CAMPOST=\"123.207.166.162/upload/";//本指令与下一条指令通过HTTP的POST方式把拍摄的照片发送到特定服务器 unsigned char AT_CAMUPLOAD2[] = "\",11009\r";const unsigned char table_CamInit[] = "Camera";const unsigned char table_CamCap[] = " Press A to cap";const unsigned char table_CamWait[] = "Wait";const unsigned char table_CamEmail[] = "Email Addr";const unsigned char table_CamFinish[] = "Sending Picture";unsigned char input;display_string(table_CamInit);lcd_write_cmd(0x80 + 0x40);display_string(table_CamCap);while(1){input = keyboard_scan();//等待用户按下按键A if(input == 'A'){break;}}USART_transmit_str(AT_CAMCAP);//拍照 clear_display();display_string(table_CamWait);_delay_ms(3000);//等待三秒钟拍照时间 clear_display();display_string(table_CamEmail);lcd_write_cmd(0x80 + 0x40);keyboard_inputStr();//通过键盘输入字符串,作为发送的邮箱地址 USART_transmit_str(AT_CAMUPLOAD1);//这三条指令负责发送HTTP请求。请求格式为ip:port/emailAddr USART_transmit_str(array_str);USART_transmit_str(AT_CAMUPLOAD2);clear_display();display_string(table_CamFinish);}void GSM_mail(void){clear_display();const unsigned char table_mail[] = "Send email";display_string(table_mail);const unsigned char table_MAddr[] = "Addr";const unsigned char table_MSub[] = "Subject";const unsigned char table_MContent[] = "Content:";const unsigned char table_MSent[] = "Email Sent!";unsigned char AT_CIPSTART[] = "AT+CIPSTART=\"TCP\",\"123.207.166.162\",11002\r";//连接TCP/IP服务器 unsigned char AT_CIPSEND[] = "AT+CIPSEND=1\r";//发送数据。具体参见AT指令手册unsigned char AT_CIPCLOSE[] = "AT+CIPCLOSE\r";//关闭TCP连接 USART_transmit_str(AT_CIPSTART);//发起TCP连接 _delay_ms(5000);clear_display();display_string(table_MAddr);lcd_write_cmd(0x80 + 0x40);USART_transmit_str(AT_CIPSEND);keyboard_inputStr();//输入发送的邮件地址 USART_transmit_str(array_str);clear_display();display_string(table_MSub);lcd_write_cmd(0x80 + 0x40);USART_transmit_str(AT_CIPSEND);keyboard_inputStr();//输入发送的邮件主题 USART_transmit_str(array_str);clear_display();display_string(table_MContent);lcd_write_cmd(0x80 + 0x40);USART_transmit_str(AT_CIPSEND);keyboard_inputStr();//输入发送的邮件内容 USART_transmit_str(array_str);clear_display();display_string(table_MSent);USART_transmit_str(AT_CIPCLOSE);_delay_ms(2000);}void GSM_SMS(void){const unsigned char table_inNum[] = "Enter number:";const unsigned char table_inText[] = "Enter Content:";const unsigned char table_success[] = "MSG sent!";unsigned char AT_CMGF[] = "AT+CMGF=1\r";//发送短信的设置。具体含义参考AT指令集 unsigned char AT_CSCS[] = "AT+CSCS=\"GSM\"\r";//设置短信格式 unsigned char AT_CMGS[] = "AT+CMGS=\"";//发送短信前缀。此指令后接电话号码再接回车符即为发送短信的AT指令 clear_display();display_string(table_inNum);lcd_write_cmd(0x80 + 0x40);keyboard_inputNum();//输入收信人号码 USART_transmit_str(AT_CMGF);_delay_ms(50);USART_transmit_str(AT_CSCS);_delay_ms(50);USART_transmit_str(AT_CMGS);USART_transmit_str(array_num);USART_transmit_byte('\"');USART_transmit_byte('\r');//这几条串口发送的数据构成完整的AT指令 _delay_ms(50);clear_display();display_string(table_inText);lcd_write_cmd(0x80 + 0x40);keyboard_inputStr();//输入短信内容 USART_transmit_str(array_str);USART_transmit_byte(0x1a);//短信内容的结束必需以0x1A为标志 USART_transmit_byte('\n');_delay_ms(1000);clear_display();display_string(table_success);_delay_ms(3000);}void GSM_call(void){clear_display();const unsigned char table_CallInNum[] = "Enter number:";unsigned char AT_ATH[] = "ATH\r";//挂断指令 unsigned char AT_ATD[] = "ATD";//拨打电话指令前缀,后接号码再接回车符构成拨打电话的AT指令 unsigned char input;display_string(table_CallInNum);lcd_write_cmd(0x80 + 0x40);keyboard_inputNum();//输入数字串,作为拨打的电话号码 USART_transmit_str(AT_ATD);USART_transmit_str(array_num);USART_transmit_byte('\r');_delay_ms(300);clear_display();display_string("Calling...");lcd_write_cmd(0x80 + 0x40);display_string("Press D to end");while(1){input = keyboard_scan();//死循环扫描键盘 if(input == 'D'){//键盘按D时挂断并退出 USART_transmit_str(AT_ATH);break;}}}void GSM_RcCall(void){//接电话 clear_display();unsigned char flag = 0, input, index = 1, j = 0, number[LIST_NUM_LENGTH];unsigned char AT_ATA[] = "ATA\r";//接听电话 unsigned char AT_ATH[] = "ATH\r";//挂断电话 while(array_rc[index]){if(array_rc[index] == 'I' && array_rc[index - 1] == 'L'){//搜索'LI',找到之后查找'LI'后面的双引号,双引号后面就是手机号 flag = 1;}if(array_rc[index++] == '\"'){if(flag){while(array_rc[index] != '\"'){number[j++] = array_rc[index++];}number[j] = 0;break;}}}const unsigned char table_Caller[] = "Tel:";const unsigned char table_pick[] = "A:Pick D:Return";display_string(table_Caller);display_string(number);lcd_write_cmd(0x80 + 0x40);display_string(table_pick);while(1){input = keyboard_scan();//死循环检索键盘 if(input){if(input == 'A'){USART_transmit_str(AT_ATA);//若按A则接听 }if(input == 'D'){USART_transmit_str(AT_ATH);//若按D则挂断 break;}}}clear_display();display_string(table_Home);lcd_write_cmd(0x80 + 0x40);display_string(table_option);}//****************USART-related***********void USART_init(void){//初始化USART。原理参考书籍。 UBRRH = 0;UBRRL = 5;UCSRA = 0x00;UCSRB = 0x08;//开启TX,关闭RX。在GSM模块初始化中会重新开启RX UCSRC = 0x86;_delay_ms(2);}unsigned char receive_byte(void){//接收一个字符。原理参考书籍。 unsigned char TEMP = 0;if((UCSRA&0x80) == 0x80){TEMP = UDR;}return TEMP;}void USART_transmit_byte(unsigned char data){//发送一个字符。原理参考书籍。 while((UCSRA&0x20) == 0);UDR = data;}void USART_transmit_str(unsigned char* str){//发送一个字符串。原理参考书籍。 unsigned char NUM = 0;while(str[NUM]){USART_transmit_byte(str[NUM++]);}while((UCSRA&0x20) == 0);}void USART_RX_Proc(void){//处理array_rc(串口接收缓冲池)中的数据 unsigned char i = 0, j = 0;array_rc[array_rc_index] = 0;if(strstr(array_rc, "+CLIP: ")){//如果有“+CLIP:”,说明有电话打进来 GSM_RcCall();//进入接电话程序 }for(i = array_rc_index - 1; i > 0; i--){//在缓冲池中倒序搜索'\r'。这是一条完整信息的结束标志。 if(array_rc[i] == '\r'){break;}}//下面删除此标志之前的所有数据。 for(j = 0; j < array_rc_index - 1 - i; j++){array_rc[j] = array_rc[i + 1 + j];}array_rc_index = j;for(;j < LIST_RC_LENGTH;j++){array_rc[j] = 0;}}ISR(USART_RXC_vect){//接收中断。当串口接收到数据时进入此中断程序 if(array_rc_index < LIST_RC_LENGTH){array_rc[array_rc_index++] = UDR;//将接收到的数据存在全局字符串数组array_rc中,等待后续处理。 }}//*****************1602显示屏相关函数***************//为了保留出I2C总线口和SPI总线口,将LCD的八个数据位分开。分别使用PORTB的低四位和PORTC的高四位。 void lcd_write_cmd(uchar cmd){//向LCD写入指令。原理参考书籍。 LCDRW_CLR;LCDRS_CLR;PORTB = (cmd & 0x0f);PORTC = (cmd & 0xf0);_delay_ms(2);LCDEN_SET;_delay_ms(2);LCDEN_CLR;}unsigned char read_cmd(void){//从LCD读取光标地址。原理参考书籍。 unsigned char add;LCDRW_SET;LCDRS_CLR;DDRB &= 0xf0;DDRC &= 0x0f;_delay_ms(2);LCDEN_SET;_delay_ms(2);LCDEN_CLR;add = ((PINB & 0x0f) + (PINC & 0x70));DDRB |= 0x0f;DDRC |= 0xf0;return add;}void lcd_write_data(uchar inf){//向LCD写入数据。原理参考书籍。 unsigned char cursor_add = read_cmd();if(cursor_add == 0x10){lcd_write_cmd(0x80 + 0x40);}if(cursor_add == 0x50){clear_display();}LCDRW_CLR;LCDRS_SET;PORTB = (inf & 0x0f);PORTC = (inf & 0xf0);_delay_ms(2);LCDEN_SET;_delay_ms(2);LCDEN_CLR;}void clear_display(void){//清空显示屏。原理参考书籍。  lcd_write_cmd(0x01);_delay_ms(2);}void init_lcd(void){//初始化显示屏。原理参考书籍。  DDRB |= 0x0f; DDRC |= 0xf0;DDRD = 0xf0;LCDRW_CLR;LCDRS_CLR;LCDEN_CLR;lcd_write_cmd(0x38);lcd_write_cmd(0x0f);lcd_write_cmd(0x06);lcd_write_cmd(0x01);}void lcd_delete(void){//删除光标处前的一个字符。 unsigned char cursor_add = read_cmd();if(cursor_add == 0x40){cursor_add = 0x0f;}else if(cursor_add){cursor_add--;}lcd_write_cmd(0x80 + cursor_add);lcd_write_data(' ');lcd_write_cmd(0x80 + cursor_add);}void display_string(unsigned const char *str){//显示一个字符串 uchar NUM = 0;while(str[NUM] != 0){lcd_write_data(str[NUM++]);_delay_ms(2);}}//*************键盘相关函数**************void keyboard_init(void){//初始化键盘,矩阵键盘连接至PORTA DDRA = 0xf0;PORTA = 0xff;}unsigned char keyboard_scan(void){//矩阵键盘扫描一次,如果有按键按下则返回对应原始字符,若无按键按下则返回0 unsigned char row, col = 0, LOW;for( row = 0; row < 4; row++){PORTA = ~(1 << (row + 4));_delay_ms(5);if(~PINA & 0x0f){_delay_ms(10);LOW = ~PINA & 0x0f;if(LOW){while(LOW >>= 1){col ++;}while(~PINA & 0x0f);_delay_ms(5);while(~PINA & 0x0f);return keyboard_val[4*row + col];}}}return 0;}unsigned char keyboard_9keys(void){//九键输入函数,通过按的次数决定输出的字符。一个按键对应七个字符 unsigned char input, following, repeat = 0, loop, flag = 0;input = keyboard_scan();if(input == 'D')return 127;//预留字符D。若按下字符D,则返回127 if(input == 'A')return 126;//预留字符A。若按下字符A,则返回126if (input){if (input < 48 || input > 57){return 0;}while(1){loop = 0;while(!(following = keyboard_scan())){loop++;if(loop >= 30){flag = 1;break;}}if(flag)break;if(following == input){repeat++;}else{break;}if(repeat == 6)break;}return keyboard_9keysVal[(input - 48)*7 + repeat];}return 0;}void keyboard_inputNum(void){//输入数字串 unsigned char index = 0, input;while(1){while(!(input = keyboard_scan()));if(input == 'A')break;//按下A则结束输入 if(input == 'D'){//按下D则删除上一位输入 lcd_delete();index--;}else{array_num[index++] = input;//将输入存储在全局字符串数组array_num中 lcd_write_data(input);//将输入实时显示 }if(index == LIST_NUM_LENGTH - 1)break;}array_num[index] = 0;//在输入的最后一位之后补0,作为字符串结束符 return ;}void keyboard_inputStr(void){unsigned char index = 0, input;while(1){while(!(input = keyboard_9keys()));if(input == 126)break;//按下A则结束输入 if(input == 127){//按下D则删除上一位输入lcd_delete();index--;}else{array_str[index++] = input;//将输入存储在全局字符串数组array_str中 lcd_write_data(input);//将输入实时显示}if(index == LIST_STR_LENGTH)break;}array_str[index] = 0;//在输入的最后一位之后补0,作为字符串结束符 }


0 0
原创粉丝点击