目录

基于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

程序整体流程

https://img.yuanze.wang/posts/msp430-temp-sampler/proc-digram.png
程序流程图

在程序刚开始时,会检测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传感器模块。制作整体的外观如下图所示。

https://img.yuanze.wang/posts/msp430-temp-sampler/board1.jpg
制作整体外观

上电时,屏幕会显示学校Logo与制作人信息,然后会进入时间设置界面,左键切换下一个,右键增加数值,如下图所示:

https://img.yuanze.wang/posts/msp430-temp-sampler/board2.jpg
时间设置界面

之后,系统会等待插入内存卡。插入后,会显示内存卡剩余容量,按任意键即可进入主界面。进入主界面后,会开始显示时间、日期、温度与湿度,每隔五秒钟会保存一次数据,保存时右下角会提示正在保存。

https://img.yuanze.wang/posts/msp430-temp-sampler/board3.jpg
SD卡剩余容量显示

进入主界面后,会开始显示时间、日期、温度与湿度,每隔五秒钟会保存一次数据,保存时右下角会提示正在保存。

https://img.yuanze.wang/posts/msp430-temp-sampler/board4.jpg
主界面

保存后,即可在SD卡中找到DATA.CSV文件,使用excel打开即可看到保存的数据,如下图所示。

https://img.yuanze.wang/posts/msp430-temp-sampler/excel-screenshot.png
Excel截图

 CCS工程