资讯详情

基于 SIM800 的时间、天气语音播报服务机器人

系列文章目录

项目一 基于 SIM800 天气语音广播服务机器人的时间 等待后续添加……


文章目录


前言

移动蜂窝网络已经渗透到我们生活的方方面面,无论是即时聊天、视频电视还是移动支付,都深深依赖于移动蜂窝网络。

经过几十年的发展,移动蜂窝网从 1G 到 5G,人们的生活方式发生了翻天覆地的变化。

该项目使用 2G 实现语言播报天气和时间的服务机器人。


一、总体设计方案

1. 硬件

在这里插入图片描述

  1. 主控:STM32F103C8T6;
  2. 通信模块:SIM800模块;
  3. 显示模块:0.96 寸 OLED12864 显示屏幕;
  4. 语音模块:SYN6288 串口文字转语音模块;
  5. 独立按键模块。

2. 软件

  1. SIM800 的 HTTP 服务器获取天气和时间数据;
  2. 单片机串口通过文字发送天气和时间数据 SYN6288 模块;
  3. 按键设置闹钟和定时器功能;
  4. 显示时间和天气。

二、硬件设计

设计中使用的所有模块均采用模块组装,方便省事。

1. STM32F103C8T6 最小系统

现在芯片贼贵,原来几块钱的单片机,现在要买三四十块。

资金紧张,STM32F103C8T6 可考虑使用国产芯片代替。 STM32 PIN to PIN 有很多国产芯片可以自己玩。

2. SIM800 模块

SIM800 是一款四频 GSM/GPRS 为城堡孔包装的模块。性能稳定,外观小巧,性价比高,能满足客户的各种需求。SIM800 工作频率为GSM/GPRS850/900/1800/1900MHz,语音可以低功耗实现,SMS传输数据信息。SIM800 尺寸为17.615.72.3mm,可适用于各种紧凑型产品的设计需求。

该模块需要配合 SIM 使用卡片。不同的制造商是对的 SIM800 模块设计略有不同,有的自动复位,有的需要手动复位;SIM 大小也不一样:有的用小卡,有的用大卡。购买时注意这些参数。

现在我们日常使用的卡大多数已经是 5G 网络,至少也是 4G 是的,但基本上 2G,所以你可以用你的手机卡来测试。然而,互联网上有很多纯粹的 2G 卖卡很方便。

3. SYN6288 语音模块

这个模块没什么好说的。可以直接用串口发文字转换成语音。中英文可以直接转换。

唯一需要注意的是中文编码。该模块支持 GB2312,GBK,Unicode 等待不同的中文编码,您使用的编码与编译器有关,需要作为参数设置。

详情请参考本文:SYN6288语音合成模块介绍

数据手册:SYN6288 数据手册

4. OLED 显示模块

屏幕模块的尺寸约为 0.96 英寸主要由裸屏和底板组成PCB裸屏由组成 SSD1306驱动也被广泛使用 LED 驱动芯片。

驱动接口有 SPI 和 IIC 本设计采用两种 IIC 驱动。

5. 按键模块

直接购买四合一的独立按键模块。不知道为什么这个模块没有上拉电阻,所以使用要配置成


三、软件设计

1 时间天气服务API

网上有很多免费的天气和时间服务界面,可以自己去找。我在这里用。 NowAPI。使用方法也很简单,去官方网站注册账户,然后申请免费使用。使用时间只有三个月,我只需要做测试。高德天气服务似乎有一个长期的免费接口。 申请天气预报接口和标准北京时间接口。

附上我申请的接口(过期无效): 天气预报 北京时间 如果无效,注册一个账户并申请 API 自动生成后 AppKey 和 Sign,替换上面连接的内部 AppKey 和 Sign 就行了。

2 SIM800 HTTP 服务

使用天气预报和北京时间服务 HTTP 因此,我们需要获取服务器数据并启动需求 SIM800 的 HTTP 服务。

SIM800 使用 串口 AT 实际上,我们只需要发送几个指令控制 AT 您可以获得服务器返回给我们的数据。

/* 获取服务器数据 url 是服务 API 获得的数据存在于串口接收中 BUFF 里面 */ u8 sim900a_get_http(u8 *url) { 
          static u8 http_not_init = 1;  u8 cmd[200];    sprintf((char*)cmd, "AT HTTPPARA=\"URL\",");  strcat((char*)cmd, (char*)url);
	
	//if(sim900a_send_cmd((u8 *)"ATE0",(u8 *)"OK",100)) return SIM_CSQ_ERR;
	if(http_not_init)
	{ 
        
		http_not_init = 0;
		if(sim900a_send_cmd((u8 *)"AT+CSQ",(u8 *)"OK",100))		return SIM_CSQ_ERR;
		if(sim900a_send_cmd((u8 *)"AT+CREG?",(u8 *)"OK",100))	return SIM_CREQ_ERR;
		if(sim900a_send_cmd((u8 *)"AT+CSCA?",(u8 *)"OK",100))	return SIM_CSCA_ERR;
		if(sim900a_send_cmd((u8 *)"AT+CGATT?",(u8 *)"OK",100))	return SIM_CGATT_ERR;
		if(sim900a_send_cmd((u8 *)"AT+SAPBR=3,1,\"APN\",\"CMNET\"",(u8 *)"OK",100))	return SIM_SAPBR_ERR;
		if(sim900a_send_cmd((u8 *)"AT+SAPBR=1,1",(u8 *)"OK",1000))	return SIM_SAPBR_ERR;
		if(sim900a_send_cmd((u8 *)"AT+HTTPINIT",(u8 *)"OK",300))	return SIM_HTTPINIT_ERR;
	}
	if(sim900a_send_cmd(cmd,(u8 *)"OK",300));// return SIM_CMGS_ERR;
	if(sim900a_send_cmd((u8 *)"AT+HTTPACTION=0",(u8 *)"+HTTPACTION: 0,200",2000))	return SIM_HTTPACTION_ERR;
	sim900a_send_cmd((u8 *)"AT+HTTPREAD",(u8 *)"OK",100);
	
	return SIM_OK;
}

//向sim900a发送命令
//cmd:发送的命令字符串(不需要添加回车了),当cmd<0XFF的时候,发送数字(比如发送0X1A),大于的时候发送字符串.
//ack:期待的应答结果,如果为空,则表示不需要等待应答
//waittime:等待时间(单位:10ms)
//返回值:0,发送成功(得到了期待的应答结果)
// 1,发送失败
u8 sim900a_send_cmd(u8 *cmd,u8 *ack,u16 waittime)
{ 
        
	u8 res=0; 
	USART2_RX_STA=0;USART2_RX_REC_ATCOMMAD=1;
	if((u32)cmd<=0XFF)
	{ 
        
		while(DMA1_Channel7->CNDTR!=0);	//等待通道7传输完成 
		USART2->DR=(u32)cmd;
	}else u2_printf("%s\r\n",cmd);//发送命令
	if(ack&&waittime)		//需要等待应答
	{ 
        
		while(--waittime)	//等待倒计时
		{ 
        
			delay_ms(10);
			if(USART2_RX_STA&0X8000)//接收到期待的应答结果
			{ 
        
				if(((USART2_RX_STA & 0x7FFF) > 200) && (strstr((const char*)USART2_RX_BUF,"{")))
				{ 
        
					memset(http_buff, 0, USART2_MAX_RECV_LEN);
					strcpy((char*)http_buff, (char*)USART2_RX_BUF); //大于200字节认为是http数据
				}
				if(sim900a_check_cmd(ack))break;//得到有效数据 
				USART2_RX_STA=0;
			} 
		}
		if(waittime==0)res=1; 
	}
	USART2_RX_STA=0;USART2_RX_REC_ATCOMMAD=0;
	return res;
} 

/// 
//usmart支持部分 
//将收到的AT指令应答数据返回给电脑串口
//mode:0,不清零USART2_RX_STA;
// 1,清零USART2_RX_STA;
void sim_at_response(u8 mode)
{ 
        
	if(USART2_RX_STA&0X8000)		//接收到一次数据了
	{ 
         
		USART2_RX_BUF[USART2_RX_STA&0X7FFF]=0;//添加结束符
		printf("%s",USART2_RX_BUF);	//发送到串口
		if(mode)USART2_RX_STA=0;		
	} 
}
/// 
//ATK-SIM900A 各项测试(拨号测试、短信测试、GPRS测试)共用代码
//sim900a发送命令后,检测接收到的应答
//str:期待的应答结果
//返回值:0,没有得到期待的应答结果
// 其他,期待应答结果的位置(str的位置)
u8* sim900a_check_cmd(u8 *str)
{ 
        
	char *strx=0;
	if(USART2_RX_STA&0X8000)		//接收到一次数据了
	{ 
         
		USART2_RX_BUF[USART2_RX_STA&0X7FFF]=0;//添加结束符
		strx=strstr((const char*)USART2_RX_BUF,(const char*)str);
	} 
	return (u8*)strx;
}

3 使用 cJSON 解析服务器返回的数据

上述接口返回的数据都是 JSON 格式的,我们的单片机没法直接使用,要先把有用的数据解析出来。

已时间服务为例,获取到的数据如下:

{ 
        "success":"1","result":
{ 
        "timestamp":"1656234962","datetime_1":"2022-06-26 
17:16:02","datetime_2":"2022年06月26日 17时16分02
秒","week_1":"0","week_2":"星期日","week_3":"周
日","week_4":"Sunday"}}

这是标准的 JSON 格式数据。开头的“success”:“1” 表示获取成功,“result” 里面包含了返回的数据。

JSON 是一种很直观的数据表示方式,上面的数据不用解释想必大家也能看懂。

我们能看懂,但是单片机可看不懂。JSON 格式本身就不是给 C 语言准备的,所以解析的话相对麻烦。这里使用大神写的 cJSON 库,底层已经帮我们做好了,只要简单调用几个函数就可以解析。

cJSON 使用参考一下大佬的文章 cJSON使用详细教程。

u8 http_weather_data_parser(char *dat)
{ 
        
	cJSON* cjson_root = NULL;
    cJSON* cjson_success = NULL;
    cJSON* cjson_result = NULL;
    cJSON* cjson_weather = NULL;

	/* 解析整段JSO数据 */
	printf("%s", dat);
	cjson_root = cJSON_Parse(dat);
	if(cjson_root == NULL)
	{ 
        
		printf("Parse fail.\n");
		return PARSE_FAILED;
	}

	/* 依次根据名称提取JSON数据(键值对) */
	cjson_success = cJSON_GetObjectItem(cjson_root, "success");

	if(*cjson_success->valuestring != '1')
	{ 
        
		printf("GET_DATA_FAILED\r\n");
		cJSON_Delete(cjson_root);
		return PARSE_FAILED;
	}

	/* 解析嵌套json数据 */
	cjson_result    = cJSON_GetObjectItem(cjson_root, "result");
	cjson_weather   = cJSON_GetObjectItem(cjson_result, "weather_curr");

	strcpy((char*)weather_buf, cjson_weather->valuestring);

	for(weather_index = 0; weather_index < 10; weather_index++)
	{ 
        
		if(strcmp((char*)&weather_matrix[weather_index][0], (char*)weather_buf) == 0)
		{ 
        
			break;
		}
	}
	if(weather_index > 10)
	{ 
        
		weather_index = 0;
	}

	cJSON_Delete(cjson_root);

	return GET_DATA_SUCCESS;
}

4 数据使用的坑

这里有个大坑:服务器返回的天气数据只有 UTF-8 的中文,没有英文;而使用 cJSON 没办法把这些中文解析出来。

尝试改编辑器的编码也无济于事,最后没办法,只好采用曲线救国的方式,手动把中文天气和十六机制编码匹配。

英文天气是给 OLED 显示用的,懒得给每个天气做中文转换,就全用英文显示了。

u8 weather_matrix[][7] = { 
        
	{ 
        0xE6, 0x99, 0xB4, 0x00},					 //sunny
	{ 
        0xE5, 0xA4, 0x9A, 0xE4, 0xBA, 0x91, 0x00},  //cloudy
	{ 
        0xE9, 0x98, 0xB4, 0x00},					 //overcast
	{ 
        0xE5, 0xB0, 0x8F, 0xE9, 0x9B, 0xA8, 0x00},  // light rain
	{ 
        0xE4, 0xB8, 0xAD, 0xE9, 0x9B, 0xA8, 0x00},  // moderate rain
	{ 
        0xE5, 0xA4, 0xA7, 0xE9, 0x9B, 0xA8, 0x00},  // heavy rain
	{ 
        0xE6, 0x9A, 0xB4, 0xE9, 0x9B, 0xA8, 0x00},  // rainstorm
	{ 
        0xE5, 0xB0, 0x8F, 0xE9, 0x9B, 0xAA, 0x00},  // light snow
	{ 
        0xE4, 0xB8, 0xAD, 0xE9, 0x9B, 0xAA, 0x00},  // moderate snow
	{ 
        0xE5, 0xA4, 0xA7, 0xE9, 0x9B, 0xAA, 0x00},  // heavy snow 
	{ 
        0xE6, 0x99, 0xB4, 0x00}}; 					 //sunny 
	

u8 weather_str[][15] = { 
        "Sunny", "Cloudy", "Overcast", "Light rain", "Moderate rain", 
						"Heavy_rain", "Rainstorm", "Light snow", "Moderate snow", "Heavy snow"};

u8 weather_voice[][30] = { 
        "天气晴", "天气多云", "天气阴", "天气小雨", "天气中雨",
						  "天气大雨", "天气暴雨", "天气小雪", "天气中雪", "天气大雪"};

北京时间我这里是直接拿获取到的时间戳转换出来,不存在上述的问题。

5 显示天气和时间到显示屏

首先贴一下 SSD1306 驱动代码,这是我自己测试过能使用的。

#include "OLED_I2C.h"
#include "delay.h"
#include "codetab.h"

void I2C_Configuration(void)
{ 
        
	I2C_InitTypeDef  I2C_InitStructure;
	GPIO_InitTypeDef  GPIO_InitStructure; 

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);

	/*STM32F103C8T6芯片的硬件I2C: PB6 -- SCL; PB7 -- SDA */
	GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;//I2C必须开漏输出
	GPIO_Init(GPIOB, &GPIO_InitStructure);

	I2C_DeInit(I2C1);//使用I2C1
	I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
	I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
	I2C_InitStructure.I2C_OwnAddress1 = 0x30;//主机的I2C地址,随便写的
	I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
	I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
	I2C_InitStructure.I2C_ClockSpeed = 400000;//400K

	I2C_Cmd(I2C1, ENABLE);
	I2C_Init(I2C1, &I2C_InitStructure);
}

void I2C_WriteByte(uint8_t addr,uint8_t data)
{ 
        
	u16 cnt = 0xFFFF;
    while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY) && cnt--);
	
	I2C_GenerateSTART(I2C1, ENABLE);//开启I2C1
	while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));/*EV5,主模式*/

	I2C_Send7bitAddress(I2C1, OLED_ADDRESS, I2C_Direction_Transmitter);//器件地址 -- 默认0x78
	while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));

	I2C_SendData(I2C1, addr);//寄存器地址
	while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));

	I2C_SendData(I2C1, data);//发送数据
	while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
	
	I2C_GenerateSTOP(I2C1, ENABLE);//关闭I2C1总线
}

void WriteCmd(unsigned char I2C_Command)//写命令
{ 
        
	I2C_WriteByte(0x00, I2C_Command);
}

void WriteDat(unsigned char I2C_Data)//写数据
{ 
        
	I2C_WriteByte(0x40, I2C_Data);
}

void OLED_Init(void)
{ 
        
	delay_ms(100); //这里的延时很重要
	
	WriteCmd(0xAE); //display off
	WriteCmd(0x20);	//Set Memory Addressing Mode 
	WriteCmd(0x10);	//00,Horizontal Addressing Mode;01,Vertical Addressing Mode;10,Page Addressing Mode (RESET);11,Invalid
	WriteCmd(0xb0);	//Set Page Start Address for Page Addressing Mode,0-7
	WriteCmd(0xc8);	//Set COM Output Scan Direction
	WriteCmd(0x00); //---set low column address
	WriteCmd(0x10); //---set high column address
	WriteCmd(0x40); //--set start line address
	WriteCmd(0x81); //--set contrast control register
	WriteCmd(0xff); //亮度调节 0x00~0xff
	WriteCmd(0xa1); //--set segment re-map 0 to 127
	WriteCmd(0xa6); //--set normal display
	WriteCmd(0xa8); //--set multiplex ratio(1 to 64)
	WriteCmd(0x3F); //
	WriteCmd(0xa4); //0xa4,Output follows RAM content;0xa5,Output ignores RAM content
	WriteCmd(0xd3); //-set display offset
	WriteCmd(0x00); //-not offset
	WriteCmd(0xd5); //--set display clock divide ratio/oscillator frequency
	WriteCmd(0xf0); //--set divide ratio
	WriteCmd(0xd9); //--set pre-charge period
	WriteCmd(0x22); //
	WriteCmd(0xda); //--set com pins hardware configuration
	WriteCmd(0x12)

标签: 6870c0442b倒屏电阻

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台