基于MC9S12XS128单片机的AD数据采集系统(HC-05蓝牙无线串口传输)
说明
对多个传感器进行供电,并采集传感器输出的模拟电压信号,无线传输至电脑接收终端,对数据进行保存分析。因为应用场景为采集光敏电阻及湿度传感器等传统的传感器信号,所以这里依然选择自带ADC的MC9S12XS128作为本次设计的主控芯片。由于应用主要是简单的ADC数据采集及无线串口传输,这里选择引脚数最少的封装LQFP64。如下图所示。
硬件选型
MCU
MCU选择MC9S12XS128MAE(LQFP64),MC9S12XS128 是 16 位单片机,由 16 位中央处理单元(CPU12X)、128KB 程序、Flash(P-lash)、8KB RAM、8KB 数据 Flash(D-lash)组成片内存储器。LQFP64封装的现在某宝上有很多,但不一定都有货,价格也差别很大,下单前最好问一下客服有没有货,而且最近大部分芯片都在涨价。
无线传输
无线串口传输选用HC-05,最高可以实现1382400bps传输速率,传输距离10米,因为我们这里所接收端是电脑上的,且现在大多数台式电脑都自带蓝牙,所以HC-05只需要一个,用于连接MC9S12XS128就可以,电脑端只需要用自带的蓝牙接收HC-05的数据即可。为了使设计出的板子整体美观,这里选用贴片的HC-05,如下图所示,不同于常用的带底板的HC-05,贴片的HC-05核心板需要外围辅助电路才能正常工作。这里从HC-05使用手册上可以找到,对应的外围电路(手册上的有点糊,后面原理图上我有画,比较高清)。
电源部分
5V电源
5v电源用于板载供电,来源包括 外部供电与锂电池供电,当处于外部供电时,通过usb口电源连接至LM2940-5.0输出5v电压用于板子整体供电(这里是加5v稳压芯片是为了保证板子电源以及后续锂电池充电不受usb口输出电压的影响,usb口5v电压输入情况下,LM2490实际输出会小于5v。这里usb口设计主要是用于对锂电池充电设计,所以只要最终大于4.65v即可,为啥是4.65v马上就会说明)。LM2940封装选用TO-263下面是我在手册上复制出来的封装引脚定义以及LM2940输入与输出电压说明。(特别注意的是检查 绘制的TO-263封装4引脚连接的为GND,不要设计成与3脚OUT或者1脚IN电气相同,否则会出现板子电源正负短接)
- Input Voltage Range = 6 V to 26 V
- Dropout Voltage Typically 0.5 V at Iout=1A
为了实现无线数据采集及传输,当然需要一个内部电源对板子进行供电,而不是外接电源线进行实时供电,这样不就违背设计无线的初心了吗,那么现在需要一个内部电源进行供电,而现在锂电池的输出电压绝大部分都是3.7v的,而我们现在需要5v电压,所以肯定不能直接将锂电池接入板子上进行供电;同时,锂电池本身也需要充电,才能维持一段时间的持续供电。基于这些原因,我们需要选择一个锂电池充放电管理芯片,实现输出5v电压,以及可以为锂电池本身进行充电。这里我选用的是ESOP8封装的ip5306,该芯片可以实现5v 2.4A输出,输入电压范围为4.65v~5.5v。满足我们当前设计要求。当然我这里选择ip5306的原因一方面是因为引脚数少,方便焊接,一方面是方便后续的检测。如果想DIY设计更高级的,也可以选择比较强的芯片代替,比如支持多种快充协议的。大家按需选择。下图是Ip5306典型应用原理图。
3.3V电源
电路上的3.3v电源是对HC-05核心板进行供电使用,这里选用SOT-223封装的AMS1117-3.3,该芯片输入接上面所设计的5v电源,输出的3.3v电源对HC-05进行供电。同样需要注意下图红色标识引脚为Vout,而不是GND。
串口部分
对比了CH340系列的串口芯片,选用最简的CH340N芯片,封装为sop-8,内置独立的收发缓冲区,支持通讯波特率100bps~2Mbps,这里2Mbps在串口通信里是相当大的了。
需要注意的是,8引脚,大多数板子电源都为5v,所以,按照说明要求接0.1uf的退耦电容,不然在串口通信时会出现各种bug,尤其是通信波特率高的时候。1、2引脚,分别接接D+、D-。而6脚TXD接MCU的RXD,7脚RXD接TXD,这里需要特别注意,相同的连在一起将无法传输数据。
CH340N串口引脚 | MCU串口引脚 |
---|---|
6-TXD | 50(PS0)-RXD0 |
7-RXD | 51(PS1)-TXD0 |
硬件设计
原理图
总体原理图设计如上图所示。
渲染图是用keyshot9软件渲染的。
在 硬件选型 部分已经进行相关介绍,这里就不进行赘述了。在上面全部硬件焊接完成后,可以进行将锂电池接上,将供电开关S1拨至锂电池侧进行供电,正常情况下,锂电池电量LED指示灯点亮,以及电路电源指示灯点亮。连接上micro-usb数据线,将s2开关拨至DC电源侧,电池开始充电,电量指示灯最高指示灯开始闪烁,说明充电电路正常运行。
蓝牙HC-05配置
下面我们先对HC-05进行配置
HC-05进入AT模式
拨码开关sw4 1、2打开,其它保持关闭状态,先让HC-05的AT引脚置高进入AT模式,即按住按键sw1,接上micro-usb数据线,另一端接上电脑的usb口上进行上电,松开sw1,当D1灯变为慢闪,则表明HC-05已经进入AT模式;
查找COM号
我的电脑->右键->管理 打开界面如下,选择 设备管理器,下拉 端口,可以看到所连接板子的端口号为COM19(带CH340字样的端口)。
开始AT模式调试
打开电脑的串口调试助手,设置波特率为38400bps(这里的波特率是HC-05进入AT模式后固定的)。
配置蓝牙 (发送以下AT指令后返回OK表示设置成功)
- 恢复默认设置:AT+ORGL\r\n(\r\n即回车、换行,在串口调试助手上输入一个回车即可)
- 配置蓝牙的名称:AT+NAME=Bluetooth_M\r\n
- 配置蓝牙的配对码:AT+PSWD=1234\r\n(配对码设置为四位,需要记住,后面电脑连接配对时需要用到)
- 将蓝牙A配置为主机模式:AT+ROLE=1\r\n(板子实时采集数据并主动向外发送,所以设置为主机模式)
- 配置波特率、停止位和校验位:AT+UART=115200,0,0\r\n,设置蓝牙通信串口波特率为115200,停止位1位,无校验位(这里设置的波特率是当HC-05正常工作时的通信速率,而不是更改AT模式下固定的38400bps波特率)
电脑连接HC-05进行配对
上一步中,配置的蓝牙名称为Bluetooth_M、配对码为1234,一会我们会用到。
先对板子重新上电,使得HC-05进入正常模式
打开电脑蓝牙设置,电脑自带蓝牙的话,在桌面右下角可以看到蓝牙的图标,左键单击,选择 添加蓝牙设备,则可以打开如下界面,左键单击 添加蓝牙或其他设备,
弹出如下列表,选择第一项,等待一会,就可以看到Bluetooth_M设备名称,左键单击,将会进行配对,将会弹出输入配对码的窗口,此时输入上一步设置的1234配对码,确认后便可配对上。如果未搜索到Bluetooth_M设备,请确认是否重新上电重启板子。
至此,完成蓝牙的设置及配对工作,接下来,进行MCU程序编写。
亚克力外壳设计
为了提高板子本身的应用环境的绝缘抗噪性能,我这里将PCB导出的.stp文件导入进Rhino7(犀牛7),简单画了一下外壳,由四块亚克力板组成,外壳形状相信大家都看到了,下面内嵌的2600mAh锂电池尺寸为(7.1mm 46mm 70mm)。四块亚克力外壳加工.ai文件,在文章的最后,也会一并分享给大家。
程序设计
文章最后会把该程序的工程文件打包分享给大家。
程序设计主要思路是通过MC9S12XS128的ADC口进行8通道数据采集(LQFP64封装只有PAD00~PAD07八个ADC引脚)。这里需要特别注意使用printf时
printf("%.3f,%.3f,%.3f,%.3f\n",channel1,channel2,channel3,channel4);
需要在main.c文件上加入TERMIO_PutChar函数,不需要在主函数中调用。否则无法串口打印输出数据。至于为什么可以看这篇文章
void TERMIO_PutChar(char C)
{
while(!(SCI0SR1&0x80));
SCI0DRL=C;
}
本次程序设计的是通过8路ADC实现4路差分信号的采集,并实时通过串口输出,在这里即为HC-05串口输出。
这里是串口调试助手接收的数据截图,可以看到可以正常接收数据。
关于程序是如何编写的我也不过多的说明,在下面的源代码中都有相应的中文注释,帮助理解。
在这里安利一下,如果大家目前想使用飞思卡尔MC9S12XS128这个芯片进行系统设计的话,一方面肯定是参考官方提供的最为权威,但我推荐入门看MC9S12单片机原理及嵌入式应用开发技术书上的讲解及示例都非常好,毕竟是中文的,,我在某东和某宝查了一下,现在出第二版的了,有条件的读者推荐购买纸质的,阅读体验肯定比电子版的好,我现在只找到第一版的分享给大家。
主程序main.c
#include <hidef.h>
#include "derivative.h"
#include <stdio.h>
#include "init.h"
void TERMIO_PutChar(char C)
{
while(!(SCI0SR1&0x80));
SCI0DRL=C;
}
void main(void)
{
float channel1,channel2,channel3,channel4;
DisableInterrupts; //关闭中断
INIT_AD(); //AD初始化,八通道,8位
INIT_PLL();
INIT_SCI0();
EnableInterrupts; //打开中断
for(;;)
{
while(!(ATD0STAT0&0X80)); //查询ATD是否完成
channel1=(float)(channel_1L-channel_2L)*5/255;
channel2=(float)(channel_3L-channel_4L)*5/255;
channel3=(float)(channel_5L-channel_6L)*5/255;
channel4=(float)(channel_7L-channel_8L)*5/255;
printf("%.3f,%.3f,%.3f,%.3f\n",channel1,channel2,channel3,channel4);
}
}
init.h
#include <hidef.h>
#define channel_1H ATD0DR0H
#define channel_1L ATD0DR0L
#define channel_2H ATD0DR1H
#define channel_2L ATD0DR1L
#define channel_3H ATD0DR2H
#define channel_3L ATD0DR2L
#define channel_4H ATD0DR3H
#define channel_4L ATD0DR3L
#define channel_5H ATD0DR4H
#define channel_5L ATD0DR4L
#define channel_6H ATD0DR5H
#define channel_6L ATD0DR5L
#define channel_7H ATD0DR6H
#define channel_7L ATD0DR6L
#define channel_8H ATD0DR7H
#define channel_8L ATD0DR7L
void INIT_PLL(void);
void INIT_SCI0(void);
void SCI0_send(unsigned char data);
unsigned char SCI0_receive(void);
void INIT_SCI1(void);
void SCI1_send(unsigned char data);
unsigned char SCI1_receive(void);
void init_pwm(void);
void delay(void);
void INIT_AD(void);
unsigned char AD_capture(unsigned char chanel);
void initialize_tim(void);
void DFlash_Init(void);
void DFlash_Write(word ADDR16,word a,word b,word c,word d);
unsigned int DFlash_Read (word destination);
void DFlash_Erase(word ADDR16);
void SPI_Init(void);
byte SPI_Byte(byte value);
init.c
#include "derivative.h" /* derivative-specific definitions */
#include "init.h"
#define BUS_CLOCK 80000000 //总线频率,改变总线频率直接在此处修改
#define OSC_CLOCK 16000000 //晶振频率
#define BAUD 115200 //串口波特率
#define READword(address) ((unsigned int)(*(volatile unsigned int *__near)(address)))
#define DFLASH_LOWEST_START_PAGE 0x00 //定义data flash的起始页
#define DFLASH_START 0x00100000 //定义data flash的起始地址
#define DFLASH_PAGE_SIZE 0x0400 //定义data flash的大小为1K.
#define DFLASH_PAGE_WINDOW_START 0x0800 //定义data flash页面窗口的起始地址
unsigned int duoji=7500;
unsigned int motor=1000;
/*************************************************************/
/* 初始化锁相环 */
/*************************************************************/
void INIT_PLL(void)
{
CLKSEL &= 0x7f; //设置OSCCLK作为系统时钟
PLLCTL &= 0x8F; //禁止锁相环
//PLLCLK=2×OSCCLK×(SYNR+1)/(REFDV+1), fbus=PLLCLK/2
#if(BUS_CLOCK == 120000000)
SYNR = 0xcd;
#elif(BUS_CLOCK == 104000000)
SYNR = 0xcc;
#elif(BUS_CLOCK == 96000000)
SYNR = 0xcb;
#elif(BUS_CLOCK == 88000000)
SYNR = 0xca;
#elif(BUS_CLOCK == 80000000)
SYNR = 0xc9;
#elif(BUS_CLOCK == 72000000)
SYNR = 0xc8;
#elif(BUS_CLOCK == 64000000)
SYNR = 0xc7;
#elif(BUS_CLOCK == 56000000)
SYNR = 0xc6;
#elif(BUS_CLOCK == 48000000)
SYNR = 0xc5;
#elif(BUS_CLOCK == 40000000)
SYNR = 0x44;
#elif(BUS_CLOCK == 32000000)
SYNR = 0x43;
#elif(BUS_CLOCK == 24000000)
SYNR = 0x42;
#elif(BUS_CLOCK == 16000000)
SYNR = 0x01;
#endif
REFDV = 0x81;
PLLCTL |=0x70; //使能锁相环
asm NOP;
asm NOP;
while(!(CRGFLG&0x08)); //PLLCLK锁定
CLKSEL |= 0x80; //设置PLLCLK为系统时钟
}
/*************************************************************/
/* 初始化SCI0 */
/*************************************************************/
void INIT_SCI0(void)
{
SCI0BD = BUS_CLOCK/16/BAUD; //设置SCI0波特率为115200
SCI0CR1 = 0x00; //设置SCI0为正常模式,八位数据位,无奇偶校验
SCI0CR2 = 0x0c; //允许接收和发送数据
}
/*************************************************************/
/* 串口发送函数 */
/*************************************************************/
void SCI0_send(unsigned char data)
{
while(!SCI0SR1_TDRE); //等待发送数据寄存器(缓冲器)为空
SCI0DRL = data;
}
/*************************************************************/
/* 串口接收函数 */
/*************************************************************/
unsigned char SCI0_receive(void)
{
while(!SCI0SR1_RDRF); //等待发送数据寄存器满
return(SCI0DRL);
}
/*************************************************************/
/* 初始化SCI1 */
/*************************************************************/
void INIT_SCI1(void)
{
SCI1BD = BUS_CLOCK/16/BAUD; //设置SCI1波特率为115200
SCI1CR1 = 0x00; //设置SCI1为正常模式,八位数据位,无奇偶校验
SCI1CR2 = 0x0c; //允许接收和发送数据
}
/*************************************************************/
/* 串口发送函数 */
/*************************************************************/
void SCI1_send(unsigned char data)
{
while(!SCI1SR1_TDRE); //等待发送数据寄存器(缓冲器)为空
SCI1DRL = data;
}
/*************************************************************/
/* 串口接收函数 */
/*************************************************************/
unsigned char SCI1_receive(void)
{
while(!SCI1SR1_RDRF); //等待发送数据寄存器满
return(SCI1DRL);
}
/*************************************************************/
/* 初始化PWM */
/*************************************************************/
void init_pwm(void)
{
PWMCTL_CON01= 1; //通道01为16位的PWM
PWMCTL_CON23= 1; //通道23为16位的PWM
PWMCTL_CON45= 1; //通道45为16位的PWM
PWMPOL_PPOL1= 1; //通道的极性为高电平有效
PWMPOL_PPOL3= 0; //通道的极性为低电平有效,这一点很重要。
PWMPOL_PPOL5= 1; //通道的极性为高电平有效
PWMPRCLK = 0x33; //A时钟和B时钟的分频系数为8,频率为10MHz
PWMSCLA = 1; //SA时钟频率为5MHz
PWMSCLB = 1; //SB时钟频率为5MHz
PWMCLK = 0x20; //45用SA时钟作为时钟源,01和23用A,B时钟
PWMCAE = 0x00; //脉冲模式为左对齐模式
PWMPER01 = 2000; //通道01的频率为5KHz,用于H桥的驱动
PWMPER23 = 2000; //通道23的频率为5KHz,用于H桥的驱动
PWMPER45 = 50000; //周期10ms,用于舵机的驱动
PWMDTY01 = motor; //通道01的占空比设置
PWMDTY23 = motor; //通道23的占空比设置
PWMDTY45 = duoji; //通道45的占空比设置
PWME_PWME1=0; //禁能通道01,使用时再将该位置一
PWME_PWME3=0; //禁能通道23,使用时再将该位置一
PWME_PWME5=0; //禁能通道45,使用时再将该位置一
}
/*************************************************************/
/* 延时函数 */
/*************************************************************/
void delay(void)
{
unsigned int i;
for(i=0;i<50;i++)
{
asm("nop");
}
}
/*************************************************************/
/* 初始化AD模块 */
/*************************************************************/
void INIT_AD(void)
{
ATD0CTL1 = 0x00;
ATD0CTL2 = 0x40;
ATD0CTL3 = 0xc0;
ATD0CTL4 = 0x02;
ATD0CTL5 = 0x30;
}
/*************************************************************/
/* 起动AD转换 */
/*************************************************************/
unsigned char AD_capture(unsigned char chanel)
{
unsigned char AD_data;
switch(chanel)
{
case 0:
ATD0CTL5 = 0x00; //转换AD00
while(!ATD0STAT0_SCF);
AD_data = ATD0DR0L;
break;
case 1:
ATD0CTL5 = 0x01; //转换AD01
while(!ATD0STAT0_SCF);
AD_data = ATD0DR0L;
break;
case 2:
ATD0CTL5 = 0x02; //转换AD02
while(!ATD0STAT0_SCF);
AD_data = ATD0DR0L;
break;
case 3:
ATD0CTL5 = 0x03; //转换AD03
while(!ATD0STAT0_SCF);
AD_data = ATD0DR0L;
break;
case 4:
ATD0CTL5 = 0x04; //转换AD04
while(!ATD0STAT0_SCF);
AD_data = ATD0DR0L;
break;
case 5:
ATD0CTL5 = 0x05; //转换AD05
while(!ATD0STAT0_SCF);
AD_data = ATD0DR0L;
break;
case 6:
ATD0CTL5 = 0x06; //转换AD06
while(!ATD0STAT0_SCF);
AD_data = ATD0DR0L;
break;
case 7:
ATD0CTL5 = 0x07; //转换AD07
while(!ATD0STAT0_SCF);
AD_data = ATD0DR0L;
break;
case 8:
ATD0CTL5 = 0x08; //转换AD08
while(!ATD0STAT0_SCF);
AD_data = ATD0DR0L;
break;
case 9:
ATD0CTL5 = 0x09; //转换AD09
while(!ATD0STAT0_SCF);
AD_data = ATD0DR0L;
break;
case 10:
ATD0CTL5 = 0x0a; //转换AD10
while(!ATD0STAT0_SCF);
AD_data = ATD0DR0L;
break;
case 11:
ATD0CTL5 = 0x0b; //转换AD11
while(!ATD0STAT0_SCF);
AD_data = ATD0DR0L;
break;
case 12:
ATD0CTL5 = 0x0c; //转换AD12
while(!ATD0STAT0_SCF);
AD_data = ATD0DR0L;
break;
case 13:
ATD0CTL5 = 0x0d; //转换AD13
while(!ATD0STAT0_SCF);
AD_data = ATD0DR0L;
break;
case 14:
ATD0CTL5 = 0x0e; //转换AD14
while(!ATD0STAT0_SCF);
AD_data = ATD0DR0L;
break;
case 15:
ATD0CTL5 = 0x0f; //转换AD15
while(!ATD0STAT0_SCF);
AD_data = ATD0DR0L;
break;
}
return(AD_data);
}
/************************************************************/
/* 初始化TIM模块 */
/************************************************************/
void initialize_tim(void)
{
TSCR1_TFFCA = 1; // 定时器标志位快速清除
TSCR1_TEN = 1; // 定时器使能位. 1=允许定时器正常工作; 0=使主定时器不起作用(包括计数器)
TIOS = 0xfe; //指定通道0为输入捕捉方式,其余通道为输出比较方式
TCTL4 = 0x01; // 设置通道0为捕捉上升沿方式
TIE = 0x01; // 允许通道0定时中断
TSCR2 = 0x07; // 预分频系数pr2-pr0:111,时钟周期为1.6us,
TFLG1 = 0xff; // 清除各IC/OC中断标志位
TFLG2 = 0xff; // 清除自由定时器中断标志位
}
/*************************************************************/
/* 初始化DFLASH */
/* DFLASH的相对地址范围为0x0000-0x1fff */
/*************************************************************/
void DFlash_Init(void)
{
while(FSTAT_CCIF==0); //等待正在处理的FLASH操作完成
FCLKDIV=0x0F; //外部晶振为16M.FLASH时钟不超过1M,具体参照手册
FCNFG=0x00; //禁止中断
while(FCLKDIV_FDIVLD==0); //等待时钟设置成功
}
/*************************************************************/
/* 向DFLASH写入数据 */
/* ADDR16为写入数据的首地址 */
/* a,b,c,d为写入的数据 */
/*************************************************************/
void DFlash_Write(word ADDR16,word a,word b,word c,word d)
{
while(FSTAT_CCIF==0);
if(FSTAT_ACCERR) //判断并清除标志位;
FSTAT_ACCERR=1;
if(FSTAT_FPVIOL) //判断并清除标志位;
FSTAT_FPVIOL=1;
FCCOBIX_CCOBIX=0x00;
FCCOB=0x1110; //写入命令和高位地址
FCCOBIX_CCOBIX=0x01; //地址后16位
FCCOB=ADDR16; //写入低16位地址
FCCOBIX_CCOBIX=0x02; //写入第一个数据
FCCOB=a;
FCCOBIX_CCOBIX=0x03; //写入第二个数据
FCCOB=b;
FCCOBIX_CCOBIX=0x04; //写入第三个数据
FCCOB=c;
FCCOBIX_CCOBIX=0x05; //写入第四个数据
FCCOB=d;
FSTAT_CCIF=1; //写入执行命令
while(FSTAT_CCIF==0); //等待执行完毕
}
/*************************************************************/
/* 由DFLASH读取数据 */
/* destination为读取数据的地址 */
/*************************************************************/
word DFlash_Read (word destination)
{
byte lastepage; //用于存储EPAGE的值
byte epage; //用于计算EPAGE的值
unsigned int data; //读取出的数据
lastepage = EPAGE; //保存EPAGE的值
epage = (byte)((DFLASH_LOWEST_START_PAGE)+(destination >>10)); //计算EPAGE
EPAGE=epage; //给EPAGE赋值
data = READword((destination & (DFLASH_PAGE_SIZE - 1)) + DFLASH_PAGE_WINDOW_START); //读取页面窗口中的数据
EPAGE= lastepage; //恢复EPAGE的值
return(data);
}
/*************************************************************/
/* 擦除DFLASH的一个分区 */
/*************************************************************/
void DFlash_Erase(word ADDR16)
{
while(FSTAT_CCIF==0);
if(FSTAT_ACCERR) //判断并清除标志位;
FSTAT_ACCERR=1;
if(FSTAT_FPVIOL) //判断并清除标志位;
FSTAT_FPVIOL=1;
FCCOBIX_CCOBIX=0x00;
FCCOB=0x1210; //写入擦除命令和高位地址
FCCOBIX_CCOBIX=0x01;
FCCOB=ADDR16; //写入低16位的地址
FSTAT_CCIF=1; //启动执行命令
while(FSTAT_CCIF==0); //等待执行完成
}
/*************************************************************/
/* 初始化SPI模块 */
/*************************************************************/
void SPI_Init(void)
{
DDRS = 0xE0; //设置接口为输出
SPI0CR2 = 0x00; //SS管脚为普通I/O
SPI0CR1 = 0x5e; //使能SPI,设置为主模式
SPI0BR = 0x83; //设置波特率为1M
}
/*************************************************************/
/* SPI读写一个字节 */
/*************************************************************/
byte SPI_Byte(byte value)
{
while (!SPI0SR_SPTEF);
SPI0DRL = value;
while(!(SPI0SR_SPIF));
return SPI0DRL;
}
关于程序下载
编译与下载程序需要软件codewarrior以及BDM下载器(飞翔)
BDM驱动安装可以看我之前写的文章里面有相关的介绍,也可以配合看MC9S12单片机原理及嵌入式应用开发技术第三章中安装教程。
包括驱动文件下载,及codewarrior的下载链接,这里就不重复了。