通过读取短信息校准流量

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="5dp" >

    <Button
        android:id="@+id/btn_check_flow"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="进行流量校准"
        android:textColor="@color/black"
        android:textSize="17sp" />

    <TextView
        android:id="@+id/tv_check_flow"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="5dp"
        android:textColor="@color/black"
        android:textSize="17sp" />

</LinearLayout>
package com.example.forth;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;

import android.os.Handler;
import android.telephony.SmsManager;
import android.view.View;
import android.widget.TextView;


public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private static final String TAG = "ContentObserverActivity";
    private static TextView tv_check_flow;
    private static String mCheckResult;



    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_content_observer);
        tv_check_flow = findViewById(R.id.tv_check_flow);
        tv_check_flow.setOnClickListener(this);
        findViewById(R.id.btn_check_flow).setOnClickListener(this);
        initSmsObserver();
    }
    // 跳到系统的短信发送页面,由用户手工编辑与发送短信
    public void sendSmsManual(String phoneNumber, String message) {
        Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.parse("smsto:" + phoneNumber));
        intent.putExtra("sms_body", message);
        startActivity(intent);
    }

    // 短信发送事件
    private String SENT_SMS_ACTION = "com.example.storage.SENT_SMS_ACTION";
    // 短信接收事件
    private String DELIVERED_SMS_ACTION = "com.example.storage.DELIVERED_SMS_ACTION";


    // 无需用户操作,由App自动发送短信
    public void sendSmsAuto(String phoneNumber, String message) {
        // 以下指定短信发送事件的详细信息
        Intent sentIntent = new Intent(SENT_SMS_ACTION);
        sentIntent.putExtra("phone", phoneNumber);
        sentIntent.putExtra("message", message);
        PendingIntent sentPI = PendingIntent.getBroadcast(this, 0, sentIntent, PendingIntent.FLAG_UPDATE_CURRENT);
        // 以下指定短信接收事件的详细信息
        Intent deliverIntent = new Intent(DELIVERED_SMS_ACTION);
        deliverIntent.putExtra("phone", phoneNumber);
        deliverIntent.putExtra("message", message);
        PendingIntent deliverPI = PendingIntent.getBroadcast(this, 1, deliverIntent, PendingIntent.FLAG_UPDATE_CURRENT);
        // 获取默认的短信管理器
        SmsManager smsManager = SmsManager.getDefault();
        // 开始发送短信内容。要确保打开发送短信的完全权限,不是那种还需提示的不完整权限
        smsManager.sendTextMessage(phoneNumber, null, message, sentPI, deliverPI);
    }

    private Handler mHandler = new Handler(); // 声明一个处理器对象
    private SmsGetObserver mObserver; // 声明一个短信获取的观察器对象
    private static Uri mSmsUri; // 声明一个系统短信提供器的Uri对象
    private static String[] mSmsColumn; // 声明一个短信记录的字段数组
    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.btn_check_flow) {
            //查询数据流量,移动号码的查询方式为发送短信内容“18”给“10086”
            //电信和联通号码的短信查询方式请咨询当地运营商客服热线
            //跳到系统的短信发送页面,由用户手工发短信
            //sendSmsManual("10086", "18");
            //无需用户操作,自动发送短信
            sendSmsAuto("10086", "CXLL");
        } else if (v.getId() == R.id.tv_check_flow) {
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setTitle("收到流量校准短信");
            builder.setMessage(mCheckResult);
            builder.setPositiveButton("确定", null);
            builder.create().show();
        }
    }



    // 初始化短信观察器
    private void initSmsObserver() {
        //mSmsUri = Uri.parse("content://sms/inbox");
        //Android5.0之后似乎无法单独观察某个信箱,只能监控整个短信
        mSmsUri = Uri.parse("content://sms");
        mSmsColumn = new String[]{"address", "body", "date"};
        // 创建一个短信观察器对象
        mObserver = new SmsGetObserver(this, mHandler);
        // 给指定Uri注册内容观察器,一旦Uri内部发生数据变化,就触发观察器的onChange方法
        getContentResolver().registerContentObserver(mSmsUri, true, mObserver);
    }

    // 在页面销毁时触发
    protected void onDestroy() {
        // 注销内容观察器
        getContentResolver().unregisterContentObserver(mObserver);
        super.onDestroy();
    }


    // 定义一个短信获取的观察器
    private static class SmsGetObserver extends ContentObserver {
        private Context mContext; // 声明一个上下文对象
        public SmsGetObserver(Context context, Handler handler) {
            super(handler);
            mContext = context;
        }

        // 观察到短信的内容提供器发生变化时触发
        public void onChange(boolean selfChange) {
            String sender = "", content = "";
            // 构建一个查询短信的条件语句,这里使用移动号码测试,故而查找10086发来的短信
            String selection = String.format("address='10086' and date>%d", System.currentTimeMillis() - 1000 * 60 * 60);
           // String selection = String.format("address='10086' and >%d", System.currentTimeMillis() - 1000 * 60 * 60);
            // 通过内容解析器获取符合条件的结果集游标
            Cursor cursor = mContext.getContentResolver().query(
                    mSmsUri, mSmsColumn, selection, null, " date desc");
            // 循环取出游标所指向的所有短信记录
            while (cursor.moveToNext()) {
                sender = cursor.getString(0);
                content = cursor.getString(1);
               break;
            }
            cursor.close(); // 关闭数据库游标
            mCheckResult = String.format("发送号码:%s\n短信内容:%s", sender, content);
            // 依次解析流量校准短信里面的各项流量数值,并拼接流量校准的结果字符串
            String flow = String.format("流量校准结果如下:\n\t总流量为:%s\n\t已使用:%s" +
                            "\n\t剩余:%s", findFlow(content, "总流量为", ","),
                    findFlow(content, "已使用", "MB"), findFlow(content, "剩余", "MB"));
            if (tv_check_flow != null) { // 离开该页面时就不再显示流量信息
                // 把流量校准结果显示到文本视图tv_check_flow上面
                tv_check_flow.setText(flow);
            }
            super.onChange(selfChange);
        }
    }

    // 解析流量校准短信里面的流量数值
    private static String findFlow(String sms, String begin, String end) {
        int begin_pos = sms.indexOf(begin);
        if (begin_pos < 0) {
            return "未获取";
        }
        String sub_sms = sms.substring(begin_pos);
        int end_pos = sub_sms.indexOf(end);
        if (end_pos < 0) {
            return "未获取";
        }
        if (end.equals(",")) {
            return sub_sms.substring(begin.length(), end_pos);
        } else {
            return sub_sms.substring(begin.length(), end_pos + end.length());
        }
    }
}

 

 

<think>好的,我现在需要帮助用户了解如何用单片机读取电位值,特别是通过ADC转换的教程。首先,我得回顾一下用户提供的引用内容,看看他们给出的信息有哪些关键点。 用户引用了四篇资料,主要涉及SPI通信控制ADC芯片、单片机ADC初始化和配置、代码实现以及电压计算和显示。看起来用户可能已经有一些基础,但需要系统化的步骤说明。我需要将这些信息整合成一个清晰的教程,分步骤解释。 首先,我应该明确ADC转换的基本原理。电位器作为模拟传感器,输出模拟电压,单片机通过ADC将其转换为数字值。这个过程需要配置ADC模块、读取数据,然后转换成实际电压值。引用中提到需要初始化ADC,设置采样时钟和参考电压,比如引用[2]和[3]提到参考电压通常为5V或3.3V,这可能根据单片机型号不同而变化。 接下来,硬件连接部分。用户可能需要知道如何将电位器连接到单片机的ADC输入引脚,以及参考电压和地的连接。引用[4]中的代码用到了XPT2046芯片,这可能是一个特定的ADC芯片,需注意是否需要外部ADC模块,比如有些单片机内置ADC,有些需要外接。 然后是软件配置,包括初始化ADC模块,设置采样率、分辨率等参数。引用[1]中的代码展示了SPI通信的步骤,比如片选信号(CS)、时钟(CLK)的控制,以及写入命令和读取数据的函数。这可能涉及到时序控制,比如延时等待转换完成,如引用[1]中的for循环和_nop_()函数。 读取ADC值的步骤需要详细说明,包括启动转换、等待转换完成、读取数据寄存器。引用[3]提到启动ADC并等待转换完成,然后获取转换值。这里可能需要解释轮询方式或者中断方式,但用户提供的引用中似乎都是轮询方式,比如循环延时。 电压转换部分,用户需要将数字值转换为实际电压。公式是电压 = (ADC值 / 最大值) * 参考电压。例如,12位ADC的最大值是4095(2^12 -1),如引用[4]中的代码所示。需要注意ADC的分辨率,不同单片机可能不同。 最后,数据显示部分,引用[2]和[4]提到了通过数码管或LCD显示电压值,或者通过串口输出。这部分可能需要用户根据实际硬件选择显示方式。 可能的问题点:用户可能不清楚如何选择ADC通道,如何配置具体的寄存器,或者如何处理SPI通信的时序。需要参考具体的单片机型号,但用户提供的代码示例中,比如引用[1]使用SPI_write和SPI_read函数,这些可能需要用户自己实现,或者依赖库函数。此外,延时的时间可能需要根据ADC芯片的规格来调整,确保转换完成。 另外,注意引用中的代码有的是用C语言,比如51单片机,可能需要特定的开发环境或库。需要提醒用户根据自己使用的单片机型号查阅相关文档,尤其是ADC模块的配置方法。 最后,用户可能需要完整的代码示例,但已有的引用提供了部分代码片段,可以整合这些例子,给出一个通用的步骤,并指出需要根据具体硬件调整的地方。</think>### 单片机实现电位器ADC读取的逐步教程 #### 1. **硬件连接** - **电位器**:将中间引脚(滑动端)连接到单片机的ADC输入引脚(如P1.0)。 - **参考电压**:将电位器的两端分别接至单片机的Vref(如5V或3.3V)和GND[^2][^3]。 - **外部ADC芯片(如需)**:若单片机无内置ADC,需通过SPI/I2C连接外部ADC(如XPT2046),并按芯片手册配置时序[^1][^4]。 #### 2. **ADC模块初始化** - **选择ADC通道**:配置ADC输入引脚对应的通道(如通道0)。 - **设置分辨率**:例如12位ADC,最大值为4095。 - **配置采样率**:根据信号频率调整采样时间,防止失真。 - **参考电压选择**:例如Vref=5V时,数字值4095对应5V[^4]。 ```c void ADC_Init() { ADCON = 0x82; // 12位模式,通道0,参考电压VDD ADDLY = 0x10; // 设置采样延迟 } ``` #### 3. **读取ADC值** - **启动转换**:发送控制命令(如SPI写入指令)或触发内置ADC的启动位[^1][^3]。 - **等待转换完成**:通过延时或中断标志判断(如检查ADCON寄存器的完成位)。 - **读取数据寄存器**:获取ADC原始值。 ```c uint16_t Read_ADC() { CS = 0; // 片选使能(外部ADC时) SPI_Write(0x94); // 发送控制字(具体指令参考芯片手册) Delay_ms(1); // 等待转换完成 uint16_t adc_value = SPI_Read(); // 读取12位数据 CS = 1; // 关闭片选 return adc_value; } ``` #### 4. **电压值计算** 使用公式将数字值转换为实际电压: $$ V_{\text{实际}} = \frac{\text{ADC值}}{4095} \times V_{\text{ref}} $$ ```c float adc_vol = 5.0 * adc_value / 4096; // 假设Vref=5V,12位ADC[^4] ``` #### 5. **数据显示(可选)** - **数码管/LCD**:将电压值格式化为字符串并显示。 - **串口输出**:通过UART发送到上位机调试。 ```c printf("ADC值: %d, 电压: %.2f V\n", adc_value, adc_vol); // 串口输出 ``` #### 完整代码示例(基于51单片机) ```c #include <reg52.h> #include <intrins.h> sbit CS = P1^4; // 外部ADC片选引脚 sbit CLK = P1^5; // SPI时钟 uint16_t xpt2046_read_adc_value(uchar cmd) { uint16_t value = 0; CS = 0; CLK = 0; // 发送8位控制字(如0x94表示通道0单端输入) for (int i=0; i<8; i++) { DIN = cmd >> (7-i) & 0x01; CLK = 1; _nop_(); CLK = 0; } // 读取12位数据 for (int i=0; i<12; i++) { CLK = 1; value <<= 1; value |= DOUT; CLK = 0; } CS = 1; return value; } void main() { while(1) { uint16_t adc_value = xpt2046_read_adc_value(0x94); // 读取电位器 float voltage = 5.0 * adc_value / 4096; // 计算电压 // 显示到数码管或串口 printf("Voltage: %.2fV\n", voltage); Delay_ms(500); } } ``` #### 注意事项 - **时序匹配**:外部ADC需严格遵循芯片手册的时序(如XPT2046的CLK频率)。 - **抗干扰**:模拟信号走线尽量短,避免数字噪声干扰。 - **校准**:若精度要求高,需通过校准消除零偏和增益误差。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值