Funpack第六期MAX32660-EVSYS制作的计步器手表
Funpack第六期,MAX32660-EVSYS,DS3232,MPU6050,OLED,计步器,表
标签
嵌入式系统
MPU
测试
happy
更新2021-03-24
732

内容介绍

    自我介绍:东莞社区工作人员,日常工作为运维。对单片机有兴趣。

    硬件介绍:MAX32660-EVSYS 美信出品。Arm Cortex-M4F内核, 工作频率96MHz; 256KB Flash Memory; 96KB SRAM; 16KB 指令缓存。有14路GPIO.两路SPI,两路IIC,两路串口。支持低功耗。MAX32660-EVSYS开发板自带DAP swd调试器,一路LED,一个按键。支持面包板。,

   应用场景:健身监测器 ; 工业传感器 ; IoT ; 便携式医疗设备; 运动手表可穿戴医疗设备。

   设计思路:按照活动要求,使用MAX32660-EVSYS开发板完成时钟和计步器功能。针对时钟功能,此开发板自带RTC功能,但是没搞明白怎么外接电池。如果不能外接电池,断电后时间信息就完全丢失了,所以额外找了个DS3232模块,做为时钟模块。时钟模块DS3232与开发板之间用IIC连接。时钟模块自带电池,可以保证时间信息不丢失。
    计步器使用MPU6050。MPU6050是一个9轴运动处理传感器。它集成了3轴MEMS陀螺仪,3轴MEMS加速度计,以及一个可扩展的数字运动处理器DMP(Digital Motion Processor)。使用IIC与开发板通讯。这款传感器网上资料非常多,并且在DMP功能中就集成了计步功能,原子的教程中有完整的例程和讲解。
    显示部分用OLED来做显示。听了硬禾的直播,看有用墨水屏来做展示的,很羡慕,但是手头没有设备,就此作罢。
    有了设计思路,遇到第一个问题:DS3232与MPU6050都是用IIC通讯,这本不是问题,但是这两个模块的IIC地址都是0x68,这就让人抓狂了,用一组IIC,无法区别两个设备。所以只能将这两个设备挂在不同的IIC上了。但是这个开发板的spi和iic1(P00,P01)使用的是同一组管脚,如果使用SPI1(p0_10,p0_11)就要用到串口1,而串口1 是接到DAP上了,如果要用,就要掰开DAP,那样会导致以后的烧写困难。权衡后,放弃使用SPI的oled,改用IIC的oled,借来了一块主控SSD1307Z的0.65寸的OLED屏幕。这样DS3232接在IIC1(p0_0,p0_1),mpu6050和oled接在IIC0(p0_9,p0_8)上。再编写一个上位机,用来和网络校正时间。上位机与开发板通过串口1通讯。上位机就用python来做,简单易懂。
    第二个问题:串口问题。这个开发板提供了例程,例程中有个函数printf可以直接通过串口将信息输出到电脑上去。也就是说开发板的例程是初始化了串口1的。直接添加串口中断函数

void UART1_IRQHandler(void)

经过测试,发现完全没有调用到函数。也就是意味着,例程的串口初始化,没有初始化串口接收中断。仔细看例程,又参考了网上的说明。才明白需要在程序中将已经初始化过的串口1,关闭,再重新初始化一次,增加接收中断。

//串口1 初始化
void UART1A_INIT(void){
	UART_Shutdown(MXC_UART_GET_UART(1));		//关闭串口1  系统在启动前就初始化了串口1
	/* Setup the interrupt */
  NVIC_ClearPendingIRQ(MXC_UART_GET_IRQ(1));
  NVIC_DisableIRQ(MXC_UART_GET_IRQ(1));
  NVIC_SetPriority(MXC_UART_GET_IRQ(1), 0);
  NVIC_EnableIRQ(MXC_UART_GET_IRQ(1));
  
  uart_cfg_t cfg;
	cfg.parity = UART_PARITY_DISABLE;
	cfg.size = UART_DATA_SIZE_8_BITS;
	cfg.stop = UART_STOP_1;
	cfg.flow = UART_FLOW_CTRL_DIS;
	cfg.pol = UART_FLOW_POL_DIS;
	cfg.baud = UART_BAUD;
	error = UART_Init(MXC_UART_GET_UART(1), &cfg, &sys_uart_cfg);			//开启串口1	
	
	read_req.data = rxdata;
  read_req.len = BUFF_SIZE;
  read_req.callback = read_cb;

  //write_req.data = txdata;
  //write_req.len = BUFF_SIZE;
  //write_req.callback = write_cb;
	UART_ReadAsync(MXC_UART_GET_UART(1), &read_req);
}

 阅读例程代码,自己对美信例程的理解为,开辟了一个缓冲区“read_req.len = BUFF_SIZE;”当串口接收到了数据,数据长度超过了缓冲区,就会调用函数UART1_IRQHandler,然后调用函数read_req,在函数read_req中可以进行自定义的处理。但是不符合自己的需求。我期望的是建立自己的环形缓冲区,当串口接收到数据后,就进入环形缓冲区,开发板程序判断缓冲区大小,若为上位机下传数据包大小,则接收正确,调用处理数据过程,若大小不对,则接收数据过程有误,丢弃数据。思前想后,将美信的缓冲区设置为1个字符大小,在read_req中将一个字符塞进环形缓冲区中。经测试满足需求^_^.

FrEESRH3p0GrrdXs_Y7XJjtrycLr
    上位机部分。上位机使用python+QT实现,可以跨平台。上位机功能简单,实现日期、时间、步数的展示。增加了一个与网络时间校时的功能。先说一下串口部分,由于开发板和上位机通讯是使用DAP模块的usb转串口功能。而USB口可能会在程序启动后插入。所以使用一个单独的线程检查串口,当有串口增加时,通过信号通知主界面可以选择串口。

serialLock=QMutex()     #创建线程锁
class FindSerial(QThread):
    sinFindNewSerialPort = pyqtSignal(str)        #定义信号
    def __init__(self):
        super(FindSerial,self).__init__()
        self.workstat=True
        self.serialdict={}


    def run(self):
        while True:
            # print(threading.currentThread(),self.workstat)
            if self.workstat:
                port_list = list(list_ports.comports())
                for port in port_list:
                    #检查 是否在列表中存在
                    if self.serialdict.get(port[0])==None:   #不存在这个端口
                        serialLock.lock()
                        self.serialdict[port[0]]=port[1]
                        serialLock.unlock()
                        self.sinFindNewSerialPort.emit(port[0]) #发送找到新串口的信号
                #比较新旧 端口列表 数目是否相同
                if len(port_list)==len(self.serialdict):
                    #数目相同 无变化
                    # print('子线程还存在着')
                    self.sleep(1)
                else:
                    #新旧端口列表有变换 清除旧列表
                    serialLock.lock()
                    self.serialdict.clear()
                    serialLock.unlock()
                    self.sinFindNewSerialPort.emit('clear')
            else:
                self.sleep(2)
@pyqtSlot()  # 打开串口  关闭串口
    def on_pushButtonSerCtl_clicked(self):
        if self.ui.pushButtonSerCtl.text().find('打开') >= 0:
            try:
                # 超时设置,None:永远等待操作,0为立即返回请求结果,其他值为等待超时时间(单位为秒)
                # 使用115200波特率
                self.ser = serial.Serial(self.ui.comboBoxPort.currentText(),
                                         baudrate=115200, bytesize=8, parity='N',
                                         stopbits=1, timeout=1)
            except serial.SerialException:
                print('错误', '打开串口出错!')
            else:
                self.ser.flush()  # 刷新缓存
                self.ui.pushButtonSerCtl.setText('关闭')
                self.ui.pushButtonUpdateTime.setEnabled(True)  # 允许网络对时
                self.ui.comboBoxPort.setEnabled(False)
                self.findSerialPortThread.workstat = False  # 让线程停止工作
                self.timer.start(40)         #刷新界面开始  100ms间隔刷新
        else:
            self.ser.flush()  # 刷新缓存
            self.ser.close()
            self.ui.pushButtonSerCtl.setText('打开')
            self.ui.pushButtonUpdateTime.setEnabled(False)
            self.ui.comboBoxPort.setEnabled(True)
            self.findSerialPortThread.workstat = True  # 让线程工作
            self.timer.stop()               #停止刷新

系统中增加一个定时器,当串口打开后,每40ms读取一次串口,读到串口数据就在界面上显示。校时按钮若被按下,就通过网络获取当前时间,然后将时间发回给下位机。
    先前做上位机打开串口后程序就报错,仔细检查发现是缓冲问题,开发板先插上电脑,就不停地向电脑串口发送数据,在程序启动后清除缓冲就好了。再考虑程序在运行期间,USB口被拔出这个异常,遇到这个异常,直接关闭串口,清除缓冲,关闭刷新,启动寻找串口的线程。

    步数监测。这块倒是很容易。网上关于MPU6050的资料非常多。而mpu6050的DMP功能里就自带了步数统计的功能。

dmp_get_pedometer_step_count(&step_count); //得到计步步数
dmp_get_pedometer_walk_time(&walk_time); //得到计步所用时间

直接调用对应的函数,就能获得步数和步行的时间,不过貌似步行的时间不太对。在网上也有很多关于步行的滤波方式,对这块没有做过多的学习。这里需要解决的是IIC通讯接口的模块处理。

uint8_t i2cInit(void){
	int error;
	const sys_cfg_i2c_t sys_i2c_cfg = NULL; /* No system specific configuration needed. */
	I2C_Shutdown(I2C0_MASTER);
	I2C_Shutdown(I2C1_MASTER);
  if((error = I2C_Init(I2C0_MASTER, I2C_STD_MODE, &sys_i2c_cfg)) != E_NO_ERROR) {
	//if((error = I2C_Init(I2C_MASTER, I2C_FAST_MODE, &sys_i2c_cfg)) != E_NO_ERROR) {
      printf("Error initializing I2C0.  (Error code = %d)\n", error);
      return 1;
  }
	if((error = I2C_Init(I2C1_MASTER, I2C_STD_MODE, &sys_i2c_cfg)) != E_NO_ERROR) {
	//if((error = I2C_Init(I2C_MASTER, I2C_FAST_MODE, &sys_i2c_cfg)) != E_NO_ERROR) {
      printf("Error initializing I2C1.  (Error code = %d)\n", error);
      return 1;
  }
  NVIC_EnableIRQ(I2C0_IRQn);
	NVIC_EnableIRQ(I2C1_IRQn);
	return 0;
}

美信例程里给了IIC的硬件驱动例程,但是和网上见到的有所区别。对照IIC的时序图,掌握好关键的几点:IIC端口,要选对IIC端口,这里驱动了两个IIC端口,一个是IIC0接mpu6050和OLED,一个是IIC1接DS3232。然后是地址,不同设备对应不同驱动地址。不过mpu6050和DS3232用了同一组地址,所以只能分不同iic端口来驱动了。然后就是寄存器,最后是命令字。搞定了IIC通讯,也就搞定了DS3232,OLED,MPU6050三个外设了。驱动相关的资料还是挺丰富的。

//IIC连续写
//addr:器件地址 
//reg:寄存器地址
//len:写入长度
//buf:数据区
//返回值:0,正常
//    其他,错误代码
uint8_t MPU_Write_Len(uint8_t addr,uint8_t reg,uint8_t len,uint8_t *buf){
	uint8_t txdata[16],error;
	uint8_t i;
	txdata[0]=reg;
	for(i=1;i<len+1;i++){
		txdata[i]=buf[i-1];
	}
	error=I2C_MasterWrite(MXC_I2C0, (addr<<1)|0, txdata, len+1, 0);
	return error-(len+1);
} 
//IIC连续读
//addr:器件地址
//reg:要读取的寄存器地址
//len:要读取的长度
//buf:读取到的数据存储区
//返回值:0,正常
//    其他,错误代码
uint8_t MPU_Read_Len(uint8_t addr,uint8_t reg,uint8_t len,uint8_t *buf){ 
	uint8_t res;
	I2C_MasterWrite(MXC_I2C0, (addr<<1)|0, &reg, 1, 0);	
	//printf("read reg=%x    len=%d    %d\n",(addr<<1)|0,len,res);
 	//return I2C_MasterRead(MXC_I2C0, (addr<<1)|1, buf, len, 0);
	res=I2C_MasterRead(MXC_I2C0, (addr<<1)|1, buf, len, 0);
	//printf("read len=%d\n",res);
	return res-len;
	
}
//IIC写一个字节 
//reg:寄存器地址
//data:数据
//返回值:0,正常
//    其他,错误代码
uint8_t MPU_Write_Byte(uint8_t reg,uint8_t data){ 
	uint16_t error;
	uint8_t txdata[2];
	txdata[0]=reg;
	txdata[1]=data;
	error=I2C_MasterWrite(MXC_I2C0, MPU_WRITE, txdata, 2, 0);
	//printf("IIC write reg=%x  echo=%d \n",reg,error);
	return error-2;
}
//IIC读一个字节 
//reg:寄存器地址 
//返回值:读到的数据
uint8_t MPU_Read_Byte(uint8_t reg){
	uint8_t data;
	data=reg;
	I2C_MasterWrite(MXC_I2C0, MPU_WRITE, &data, 1, 0);
	//printf("IIC write leng=%d\n",error);
	I2C_MasterRead(MXC_I2C0, MPU_READ, &data, 1, 0);
	//printf("IIC read leng=%d\n",error);
	return data;
}

    显示部分。本意是想用SPI接口的0.96寸的OLED屏幕,想参考Thomas的文档,完成u8g2的移植的,但是这个开发板GPIO管脚还是有限,放弃了。使用了0.65寸的小OLED。显示时间:时分秒。当有步行时,显示步行步数和步行时间。无步行时(停止时间超过20秒)显示当前时间。

    心得体会:第一次参加活度,作品还是有不少瑕疵。看直播,期待各位大神的作品,到时好好学习一下,尤其是直播中说道用墨水屏做显示,还是怦然心动。很期待。感谢硬禾学堂的活动,感受到了动手的乐趣。

 

附件下载

walkwatch.zip

团队介绍

DIY爱好者,从单片机中寻找乐趣。
团队成员
happy

评论

0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号