基于MSP430的SD卡存储温度采集器
今年小学期,我们开了一门课程学习了MSP430
。基于自己之前对STM32
的了解,自己也是好好把这款单片机的相关内容学了一遍。然后就想着要做一些东西练练手。之前自己大物课给老师做过一个温度采集设备,这一次就尝试着在MSP430
上实现类似的功能。
概述
我选用了MSP430F5529
单片机作为主控芯片,SHT20
作为温湿度传感器。MSP430F5529
单片机与SHT20传感器具有低功耗的特点,因此本系统特别适合电池供电的低功耗场合。同时,这个温度采集装置还具有SD卡存储,日期、文字显示的功能,测量数据可以实时保存在SD卡中,方便后续查看与数据分析。
软件设计
主程序部分
主程序部分主要包含初始化部分与主循环部分。初始化部分主要是对各种外设和变量进行初始化,并显示制作人信息、SD卡信息和时间调整界面。这之后便进入主循环,循环读取温湿度值并显示在屏幕上,并且每五秒写入一次温湿度数据。
int main(void)
{
uint8_t sec_last = 60;
uint32_t freespace;
FATFS* __fatfs = &fatfs;
char str[20];
WDTCTL = WDTPW | WDTHOLD;//关闭看门狗
WhiteLED_Off();
SysClk_init();
LCD_Init();
ButtomKey_init();
SHT20_Reset();
LCD_bmp(0,0,64,8,gImage_upc);//显示学校LOGO
LCD_GBK16(0,64,"中国石油");
LCD_GBK16(2,64,"大学(华");
LCD_GBK16(4,64,"东)单片");
LCD_GBK16(6,64," 机实训 ");
delay_ms(2000);
LCD_GBK16(0,0,"自动化1605王远泽");
LCD_GBK16(2,0," 1605010419 ");
LCD_GBK16(4,0," 理科1601闫增朝 ");
LCD_GBK16(6,0," 1605030524 ");
delay_ms(2000);
RTC_Init();
LCD_clear();
Clock_Adj();
LCD_clear();
while(f_mount(&fatfs,"",1))
{
LCD_GBK16(0,0,"挂载SD卡失败! ");
}
LCD_GBK16(0,0,"挂载SD卡成功! ");
f_getfree("",&freespace,&__fatfs);//获取SD卡剩余容量
sprintf(str,"剩余容量:%dMB",(unsigned int)(freespace * fatfs.csize/2/1024));//转换为字符串
LCD_GBK16(2,0,str);
while(1)
{
if(KEYL || KEYR)
{
break;
}
}
LCD_clear();
Create_File();
while(1)
{
SHT20_StartTempConv();
delay_ms(100);
temp = SHT20_ReadTemp();
SHT20_StartHumidConv();
delay_ms(100);
humid = SHT20_ReadHumid();
sprintf(str,"温度:%2d.%dC",temp/10,temp%10);
LCD_GBK16(4,0,str);
sprintf(str,"湿度:%2d%%",humid);
LCD_GBK16(6,0,str);
sprintf(str,"%04d年%02d月%02d日",RTCYEARH<<8|RTCYEARL,RTCMON,RTCDAY);
LCD_GBK16(0,8,str);
sprintf(str,"%02d:%02d:%02d",RTCHOUR,RTCMIN,RTCSEC);
LCD_GBK16(2,32,str);
if(sec_last!= RTCSEC)
{
LCD_GBK16(6,79," ");
if(record_switch && !(RTCSEC%5))//每5s保存一次
{
Append_Data();
LCD_GBK16(6,79,"保存..");
}
sec_last = RTCSEC;
}
}
}
LCD显示模块
由于需要实现汉字的显示功能,官方提供的LCD显示驱动并不能满足我们的要求,因此我们自己编写了较为完整的LCD驱动程序。这两个文件主要包括初始化函数、字符显示函数、图片显示函数以及清除显示函数以及两种字号的英文ASCII字库及50多个汉字的字库。
程序可以实现汉字的存储与显示功能。汉字事先经过取模软件,生成二进制的模型数据,每一位代表一个像素点的亮灭,将这些数据存储到单片机的ROM中,调用显示函数时即可自动从程序中查找相应的字模,读取并显示到屏幕上。
void LCD_DrawGBK16(unsigned char y,unsigned char x,char* Font)
{
unsigned char i;
unsigned char *Buff;
Buff = Get_HzMat((unsigned char*)Font);
LCD_set_position(x,y);//上半行
for(i = 0;i < Font_Size;i ++)
{
LCD_dat(Buff[i]);
}
LCD_set_position(x,y+1);//下半行
for(;i < Font_Size*2;i ++)
{
LCD_dat(Buff[i]);
}
}
unsigned char LCD_GBK16(unsigned char y,unsigned char x0,const char* str)
{
unsigned char* s = (unsigned char*)str;
unsigned char x = x0;
unsigned char* s0 = s;
while(*s)
{
if (*s < 32)//控制字符
{
if(*s == '\n')//换行
{
y += Font_Size/8;
if(y >= LCD_Y)//如果超出了8行
break;
x = x0;//光标归位
}
s++;//下一个字 ASCII占用一个字节
}
else if(*s < 128)//英文或数字
{
if(x > LCD_X-Font_Size/2)//横向溢出了
{
x = x0;//光标归位
y += Font_Size/8;
if(y >= LCD_Y)//如果超出了8行
break;
}
LCD_8_16_Char(y,x,*s);
x += Font_Size/2;//下一个字 一个ASCII占用8行
s++;//下一个字 ASCII占用一个字节
}
else//如果是汉字
{
if(x > LCD_X-Font_Size)//横向溢出了
{
x = x0;//光标归位
y += Font_Size/8;
if(y >= LCD_Y)//如果超出了8行
break;
}
LCD_DrawGBK16(y,x,(char*)s);//显示汉字
x += Font_Size;//一个汉字横向长16行
s += 2;//一个汉字占用2字节
}
}
return (unsigned char)(s - s0);//返回显示的总字符数
}
其中,字形存储使用了结构体变量数组。
struct typFNT_GB162
{
unsigned char Index[2];
char Msk[32];
};
#define hz16_num 62
const struct typFNT_GB162 hz16[] = {
{"自",0x00,0x00,0x00,0xF8,0x88,0x8C,0x8A,0x89,0x88,0x88,0x88,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0xFF,0x00,0x00,0x00,0x00},
{"动",0x40,0x44,0xC4,0x44,0x44,0x44,0x40,0x10,0x10,0xFF,0x10,0x10,0x10,0xF0,0x00,0x00,0x10,0x3C,0x13,0x10,0x14,0xB8,0x40,0x30,0x0E,0x01,0x40,0x80,0x40,0x3F,0x00,0x00},
{"化",0x00,0x80,0x60,0xF8,0x07,0x00,0x00,0x00,0xFF,0x40,0x20,0x10,0x08,0x04,0x00,0x00,0x01,0x00,0x00,0xFF,0x00,0x04,0x02,0x01,0x3F,0x40,0x40,0x40,0x40,0x40,0x78,0x00}
};
日历模块
此处运用了系统的实时时钟,进行时钟的配置以及调用,实现了钟表的功能能够设定此刻的时间。调整时钟的函数应用了指针存放需要被调整变量,以及其最大值、最小值、光标显示位置,使程序简洁易读。
uint8_t year_temp;
void Clock_Adj(void)
{
char str[20];
uint8_t cnt = 0;
uint8_t* variable_pointer[] = {&RTCHOUR,&RTCMIN,&RTCSEC,&year_temp,&RTCMON,&RTCDAY};//指向需要修改变量的指针数组
uint8_t variable_high[] = {23,59,59,99,12,31};//需要修改变量的最大值数组
uint8_t variable_low[] = {0,0,0,0,1,1};//需要修改变量最小值数组
uint8_t variable_xpos[] = {32,58,82,24,58,90};//修改变量x坐标数组
uint8_t variable_ypos[] = {2,2,2,5,5,5};//修改变量y坐标数组
year_temp = (RTCYEARH<<8|RTCYEARL)%100;
LCD_clear();
LCD_GBK16(6,0,"下一个");
LCD_GBK16(6,95,"增加");
while(1)
{
LCD_6_8(variable_ypos[cnt],variable_xpos[cnt],"**");//绘制光标
sprintf(str,"%02d:%02d:%02d",RTCHOUR,RTCMIN,RTCSEC);
LCD_GBK16(0,(128-strlen(str)*8)/2,str);//显示被调整时间的字符串
sprintf(str,"%04d年%02d月%02d日",year_temp+2000,RTCMON,RTCDAY);
LCD_GBK16(3,(128-strlen(str)*8)/2,str);
if(KEYL)//UP
{
delay_ms(50);
while(KEYL);
if(*variable_pointer[cnt] < variable_high[cnt])//若小于最大值
(*variable_pointer[cnt]) ++;//增加
else
(*variable_pointer[cnt]) = variable_low[cnt];//回到最小值
}
if(KEYR)//下一个
{
delay_ms(50);
while(KEYR);
LCD_6_8(variable_ypos[cnt],variable_xpos[cnt]," ");//绘制光标
cnt ++;//下一个数
}
if(cnt >= 6)
break;
}
RTCYEARH = (year_temp+2000) >> 8;
RTCYEARL = (year_temp+2000) & 0xFF;
}
SD文件系统与文件操作
D卡是一种高级外设,可以选择SDIO与SPI两种总线进行操作。考虑到MSP430单片机的外设情况以及为了保证速度,SD卡部分使用了硬件的SPI进行操作,参照TI官方的程序编写了SD卡的驱动程序,可以实现初始化SD卡、读取和写入一个扇区的操作。
SD卡对于我们来说只是一块存储器,想要对其进行文件的操作,就需要一个文件系统模块。这里我们选用了fatFs文件系统,它是一个专为嵌入式应用开发的FAT12/FAT16/FAT32文件系统,使用C语言编写,可以方便地移植到各种平台上。移植时只需要提供SD卡读写扇区的函数,文件系统即可提供给我们打开文件、读写文件、删除文件等函数,极大地方便了我们的应用。由于此部分网上资料很多,本文不再赘述。
SHT20传感器
SHT2传感器与单片机通过IIC总线连接,因此首先需要使用C语言实现IIC总线的通信逻辑。IIC总线最大的特点就是SDA线是双向的,需要在程序中切换方向。考虑到移植的方便性与程序的可读性,将其定义为宏的形式,如下列程序所示。
#define SCL_1 P4OUT|=BIT2 //IIC时钟引脚定义
#define SCL_0 P4OUT&=~BIT2
#define SCL_OUT P4DIR|=BIT2
#define SDA P4IN&BIT1
#define SDA_1 P4OUT|=BIT1 //IIC数据引脚定义
#define SDA_0 P4OUT&=~BIT1
#define SDA_IN P4DIR&=~BIT1
#define SDA_OUT P4DIR|=BIT1
程序整体流程
在程序刚开始时,会检测SD卡是否已经挂载成功,若不成功则会循环检测,直到SD卡被正确的插入。
LCD_clear();
while(f_mount(&fatfs,"",1))
{
LCD_GBK16(0,0,"挂载SD卡失败! ");
}
LCD_GBK16(0,0,"挂载SD卡成功! ");
f_getfree("",&freespace,&__fatfs);//获取SD卡剩余容量
sprintf(str,"剩余容量:%dMB",(unsigned int)(freespace * fatfs.csize/2/1024));//转换为字符串
LCD_GBK16(2,0,str);
之后便会执行CreateFile
函数来创建一个数据文件,并向其中写入表头。这里的Excel表格实际上使用的是CSV逗号分隔符文件,它的文件结构非常简单,本质就是一个文本文件。每一行对应Excel的一行,逗号分隔的每一个元素构成Excel的一列。
void Create_File(void)
{
FRESULT fres;
fres = f_open(&file,"DATA.CSV",FA_OPEN_EXISTING);
f_close(&file);
if(fres != FR_OK)//如果文件不存在
{
f_open(&file,"DATA.CSV",FA_OPEN_ALWAYS|FA_WRITE);//创建文件
f_printf(&file,"日期,时间,温度,湿度\r\n");
f_close(&file);
}
}
之后主函数会读取温度值,在每次读取完成后,会调用Append_Data
函数在数据文件中增加一次数据。
void Append_Data(void)
{
f_open(&file,"DATA.CSV",FA_OPEN_APPEND|FA_WRITE);//增量写入模式
f_printf(&file,"%04d-%02d-%02d,%02d:%02d:%02d,%2d.%dC,%2d%%\r\n",RTCYEARH<<8|RTCYEARL,RTCMON,RTCDAY,RTCHOUR,RTCMIN,RTCSEC,temp/10,temp%10,humid);
f_close(&file);
}
硬件设计与最终结果
本次硬件开发使用的是TI官方的开发板与拓展板,外加一个SD卡模块和一个SHT20传感器模块。制作整体的外观如下图所示。
上电时,屏幕会显示学校Logo与制作人信息,然后会进入时间设置界面,左键切换下一个,右键增加数值,如下图所示:
之后,系统会等待插入内存卡。插入后,会显示内存卡剩余容量,按任意键即可进入主界面。进入主界面后,会开始显示时间、日期、温度与湿度,每隔五秒钟会保存一次数据,保存时右下角会提示正在保存。
进入主界面后,会开始显示时间、日期、温度与湿度,每隔五秒钟会保存一次数据,保存时右下角会提示正在保存。
保存后,即可在SD卡中找到DATA.CSV文件,使用excel打开即可看到保存的数据,如下图所示。