前言
最近看到一款电子沙漏,感觉还挺好玩的,然后正好要准备过年了,闲工夫比较多,那咱也来凑凑热闹,不过看了开源广场,有个STM32版本的,但是不是STM32F103版本的,所以我在他的查考了他的代码后自己改写。
关注微信公众号--星之援工作室 发送关键字(电子沙漏)
代码含重要注释,开源,可自行移植
演示视频
stm32版本 电子沙漏
一 材料准备
1.开发板
2.MPU-6050模块
MPU6050 X 1:反馈方位
3.MAX7219点阵模块
MAX7219点阵模块 X 2:实现沙漏效果
4.按键模块
按键 X 3:实现按键功能
二 引脚使用
1.MPU-6050模块与按键模块
MPU-6050模块 | 按键模块 | ||
VCC | 3.3 | GND | GND |
GND | GND | IN1 | PB12 |
SCL | PB8 | IN2 | PB13 |
SDA | PB9 | IN3 | PB14 |
2.MAX7219与蜂鸣器
MAX7219-1 | MAX7219-2 | ||
VCC | 5 | VCC | 5 |
GND | GND | GND | GND |
DIN | PA5 | DIN | PB5 |
CS | PA4 | CS | PB4 |
CLK | PA6 | CLK | PB6 |
蜂鸣器 | |||
OUT | PB7 | GND | GND |
三 代码编写
1.MPU6050部分
IIC.c
#include "IIC.h"
#include "delay.h"
#include "stdio.h"
/**************************实现函数********************************************
*函数原型: void IIC_Init(void)
*功 能: 初始化I2C对应的接口引脚。
*******************************************************************************/
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(SCLK_GPIO_CLK | DATA_GPIO_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin = SCLK_GPIO_PIN | DATA_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(SCLK_GPIO_PORT, &GPIO_InitStructure);
IIC_SCL_1;
IIC_SDA_1;
}
// 配置双向I/O端口为输出态
static void SDA_OUT()
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(DATA_GPIO_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin = DATA_GPIO_PIN; // PC.10 DATA
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(SCLK_GPIO_PORT, &GPIO_InitStructure); // 初始化GPIOC.10
}
// 配置双向I/O端口为输入态
static void SDA_IN()
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(DATA_GPIO_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin = DATA_GPIO_PIN; // PC.10 DATA
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(DATA_GPIO_PORT, &GPIO_InitStructure); // 初始化GPIOC.10
}
/**************************实现函数********************************************
*函数原型: void IIC_Start(void)
*功 能: 产生IIC起始信号
*******************************************************************************/
void IIC_Start(void)
{
SDA_OUT(); //sda线输出
IIC_SDA_1;
IIC_SCL_1;
delay_us(4);
IIC_SDA_0;//START:when CLK is high,DATA change form high to low
delay_us(4);
IIC_SCL_0;//钳住I2C总线,准备发送或接收数据
}
/**************************实现函数********************************************
*函数原型: void IIC_Stop(void)
*功 能: //产生IIC停止信号
*******************************************************************************/
void IIC_Stop(void)
{
SDA_OUT();//sda线输出
IIC_SCL_0;
IIC_SDA_0;//STOP:when CLK is high DATA change form low to high
delay_us(4);
IIC_SCL_1;
IIC_SDA_1;//发送I2C总线结束信号
delay_us(4);
}
/**************************实现函数********************************************
*函数原型: uint8_t IIC_Wait_Ack(void)
*功 能: 等待应答信号到来
//返回值:1,接收应答失败
// 0,接收应答成功
*******************************************************************************/
uint8_t IIC_Wait_Ack(void)
{
uint8_t ucErrTime=0;
SDA_IN(); //SDA设置为输入
IIC_SDA_1;delay_us(1);
IIC_SCL_1;delay_us(1);
while(READ_SDA)
{
ucErrTime++;
if(ucErrTime>50)
{
IIC_Stop();
return 1;
}
delay_us(1);
}
IIC_SCL_0;//时钟输出0
return 0;
}
/**************************实现函数********************************************
*函数原型: void IIC_Ack(void)
*功 能: 产生ACK应答
*******************************************************************************/
void IIC_Ack(void)
{
IIC_SCL_0;
SDA_OUT();
IIC_SDA_0;
delay_us(1);
IIC_SCL_1;
delay_us(1);
IIC_SCL_0;
}
/**************************实现函数********************************************
*函数原型: void IIC_NAck(void)
*功 能: 产生NACK应答
*******************************************************************************/
void IIC_NAck(void)
{
IIC_SCL_0;
SDA_OUT();
IIC_SDA_1;
delay_us(1);
IIC_SCL_1;
delay_us(1);
IIC_SCL_0;
}
/**************************实现函数********************************************
*函数原型: void IIC_Send_Byte(uint8_t txd)
*功 能: IIC发送一个字节
*******************************************************************************/
void IIC_Send_Byte(uint8_t txd)
{
uint8_t t;
SDA_OUT();
IIC_SCL_0;//拉低时钟开始数据传输
for(t=0;t<8;t++)
{
if( (txd&0x80)>>7 )
{
IIC_SDA_1;
}
else
{
IIC_SDA_0;
}
txd<<=1;
delay_us(1);
IIC_SCL_1;
delay_us(1);
IIC_SCL_0;
delay_us(1);
}
}
/**************************实现函数********************************************
*函数原型: uint8_t IIC_Read_Byte(unsigned char ack)
*功 能: //读1个字节,ack=1时,发送ACK,ack=0,发送nACK
*******************************************************************************/
uint8_t IIC_Read_Byte(unsigned char ack)
{
unsigned char i,receive=0;
SDA_IN();//SDA设置为输入
for(i=0;i<8;i++ )
{
IIC_SCL_0;
delay_us(1);
IIC_SCL_1;
receive<<=1;
if(READ_SDA)receive++;
delay_us(1);
}
if (ack){
IIC_Ack(); //发送ACK
}
else{
IIC_NAck();//发送nACK
}
return receive;
}
/**************************实现函数********************************************
*函数原型: unsigned char I2C_ReadOneByte(unsigned char I2C_Addr,unsigned char addr)
*功 能: 读取指定设备 指定寄存器的一个值
输入 I2C_Addr 目标设备地址
addr 寄存器地址
返回 读出来的值
*******************************************************************************/
unsigned char I2C_ReadOneByte(unsigned char I2C_Addr,unsigned char addr)
{
unsigned char res=0;
IIC_Start();
IIC_Send_Byte(I2C_Addr); //发送写命令
res++;
IIC_Wait_Ack();
IIC_Send_Byte(addr); res++; //发送地址
IIC_Wait_Ack();
//IIC_Stop();//产生一个停止条件
IIC_Start();
IIC_Send_Byte(I2C_Addr+1); res++; //进入接收模式
IIC_Wait_Ack();
res=IIC_Read_Byte(0);
IIC_Stop();//产生一个停止条件
return res;
}
/**************************实现函数********************************************
*函数原型: uint8_t IICreadBytes(uint8_t dev, uint8_t reg, uint8_t length, uint8_t *data)
*功 能: 读取指定设备 指定寄存器的 length个值
输入 dev 目标设备地址
reg 寄存器地址
length 要读的字节数
*data 读出的数据将要存放的指针
返回 读出来的字节数量
*******************************************************************************/
uint8_t IICreadBytes(uint8_t dev, uint8_t reg, uint8_t length, uint8_t *data){
uint8_t count = 0;
uint8_t temp;
IIC_Start();
IIC_Send_Byte(dev); //发送写命令
IIC_Wait_Ack();
IIC_Send_Byte(reg); //发送地址
IIC_Wait_Ack();
IIC_Start();
IIC_Send_Byte(dev+1); //进入接收模式
IIC_Wait_Ack();
for(count=0;count<length;count++){
if(count!=(length-1)){
temp = IIC_Read_Byte(1); //带ACK的读数据
}
else{
temp = IIC_Read_Byte(0); //最后一个字节NACK
}
data[count] = temp;
}
IIC_Stop();//产生一个停止条件
return count;
}
/**************************实现函数********************************************
*函数原型: uint8_t IICwriteBytes(uint8_t dev, uint8_t reg, uint8_t length, uint8_t* data)
*功 能: 将多个字节写入指定设备 指定寄存器
输入 dev 目标设备地址
reg 寄存器地址
length 要写的字节数
*data 将要写的数据的首地址
返回 返回是否成功
*******************************************************************************/
uint8_t IICwriteBytes(uint8_t dev, uint8_t reg, uint8_t length, uint8_t* data){
uint8_t count = 0;
IIC_Start();
IIC_Send_Byte(dev); //发送写命令
IIC_Wait_Ack();
IIC_Send_Byte(reg); //发送地址
IIC_Wait_Ack();
for(count=0;count<length;count++){
IIC_Send_Byte(data[count]);
IIC_Wait_Ack();
}
IIC_Stop();//产生一个停止条件
return 1; //status == 0;
}
/**************************实现函数********************************************
*函数原型: uint8_t IICreadByte(uint8_t dev, uint8_t reg, uint8_t *data)
*功 能: 读取指定设备 指定寄存器的一个值
输入 dev 目标设备地址
reg 寄存器地址
*data 读出的数据将要存放的地址
返回 1
*******************************************************************************/
uint8_t IICreadByte(uint8_t dev, uint8_t reg, uint8_t *data){
*data=I2C_ReadOneByte(dev, reg);
return 1;
}
/**************************实现函数********************************************
*函数原型: unsigned char IICwriteByte(unsigned char dev, unsigned char reg, unsigned char data)
*功 能: 写入指定设备 指定寄存器一个字节
输入 dev 目标设备地址
reg 寄存器地址
data 将要写入的字节
返回 1
*******************************************************************************/
unsigned char IICwriteByte(unsigned char dev, unsigned char reg, unsigned char data){
return IICwriteBytes(dev, reg, 1, &data);
}
/**************************实现函数********************************************
*函数原型: uint8_t IICwriteBits(uint8_t dev,uint8_t reg,uint8_t bitStart,uint8_t length,uint8_t data)
*功 能: 读 修改 写 指定设备 指定寄存器一个字节 中的多个位
输入 dev 目标设备地址
reg 寄存器地址
bitStart 目标字节的起始位
length 位长度
data 存放改变目标字节位的值
返回 成功 为1
失败为0
*******************************************************************************/
uint8_t IICwriteBits(uint8_t dev,uint8_t reg,uint8_t bitStart,uint8_t length,uint8_t data)
{
uint8_t b;
if (IICreadByte(dev, reg, &b) != 0) {
uint8_t mask = (0xFF << (bitStart + 1)) | 0xFF >> ((8 - bitStart) + length - 1);
data <<= (8 - length);
data >>= (7 - bitStart);
b &= mask;
b |= data;
return IICwriteByte(dev, reg, b);
} else {
return 0;
}
}
/**************************实现函数********************************************
*函数原型: uint8_t IICwriteBit(uint8_t dev, uint8_t reg, uint8_t bitNum, uint8_t data)
*功 能: 读 修改 写 指定设备 指定寄存器一个字节 中的1个位
输入 dev 目标设备地址
reg 寄存器地址
bitNum 要修改目标字节的bitNum位
data 为0 时,目标位将被清0 否则将被置位
返回 成功 为1
失败为0
*******************************************************************************/
uint8_t IICwriteBit(uint8_t dev, uint8_t reg, uint8_t bitNum, uint8_t data){
uint8_t b;
IICreadByte(dev, reg, &b);
b = (data != 0) ? (b | (1 << bitNum)) : (b & ~(1 << bitNum));
return IICwriteByte(dev, reg, b);
}
//------------------End of File----------------------------
IIC.h
可以自己修改这里面的配置部分,都是可以自定义的
#ifndef __IIC_H
#define __IIC_H
#include "stm32f10x.h"
#define SCLK_GPIO_PORT GPIOB /* GPIO端口 */
#define SCLK_GPIO_CLK RCC_APB2Periph_GPIOB /* GPIO端口时钟 */
#define SCLK_GPIO_PIN GPIO_Pin_8 /* 连接到SCL时钟线的GPIO */
#define DATA_GPIO_PORT GPIOB /* GPIO端口 */
#define DATA_GPIO_CLK RCC_APB2Periph_GPIOB /* GPIO端口时钟 */
#define DATA_GPIO_PIN GPIO_Pin_9 /* 连接到SCL时钟线的GPIO */
驱动接口,GPIO模拟IIC
// SCL-->PB9
// SDA-->PB8
// #define SDA_IN() {GPIOB->MODER&=~(3<<(9*2));GPIOB->MODER|=0<<9*2;}
// #define SDA_OUT() {GPIOB->MODER&=~(3<<(9*2));GPIOB->MODER|=1<<9*2;}
// IO操作函数
#define IIC_SCL_0 digitalLo(SCLK_GPIO_PORT, SCLK_GPIO_PIN) // digitalLo
#define IIC_SCL_1 digitalHi(SCLK_GPIO_PORT, SCLK_GPIO_PIN) // digitalHi
#define IIC_SDA_0 digitalLo(DATA_GPIO_PORT, DATA_GPIO_PIN)
#define IIC_SDA_1 digitalHi(DATA_GPIO_PORT, DATA_GPIO_PIN)
#define READ_SDA GPIO_ReadInputDataBit(DATA_GPIO_PORT, DATA_GPIO_PIN)
// IIC所有操作函数
void IIC_Init(void); // 初始化IIC的IO口
void IIC_Start(void); // 发送IIC开始信号
void IIC_Stop(void); // 发送IIC停止信号
void IIC_Send_Byte(uint8_t txd); // IIC发送一个字节
uint8_t IIC_Read_Byte(unsigned char ack); // IIC读取一个字节
uint8_t IIC_Wait_Ack(void); // IIC等待ACK信号
void IIC_Ack(void); // IIC发送ACK信号
void IIC_NAck(void); // IIC不发送ACK信号
void IIC_Write_One_Byte(uint8_t daddr, uint8_t addr, uint8_t data);
uint8_t IIC_Read_One_Byte(uint8_t daddr, uint8_t addr);
unsigned char I2C_Readkey(unsigned char I2C_Addr);
unsigned char I2C_ReadOneByte(unsigned char I2C_Addr, unsigned char addr);
unsigned char IICwriteByte(unsigned char dev, unsigned char reg, unsigned char data);
uint8_t IICwriteBytes(uint8_t dev, uint8_t reg, uint8_t length, uint8_t *data);
uint8_t IICwriteBits(uint8_t dev, uint8_t reg, uint8_t bitStart, uint8_t length, uint8_t data);
uint8_t IICwriteBit(uint8_t dev, uint8_t reg, uint8_t bitNum, uint8_t data);
uint8_t IICreadBytes(uint8_t dev, uint8_t reg, uint8_t length, uint8_t *data);
#endif
//------------------End of File----------------------------
2.max7219部分
这部分主要用到了一个模拟 SPI 通信 ,这里我们直接用来两个模拟的SPI来独立控制设备,更加方便和精确
max7219.c
#include "stm32f10x.h"
#include "max719.h"
#include "delay.h"
uint8_t m_Intensity = 1; // LED亮度
// SPI1 读写一个字节
void SPI1_ReadWriteByte(uint8_t address, uint8_t data)
{
uint8_t i;
// 发送地址字节
for (i = 0; i < 8; i++) {
// 设置DIN引脚数据位-写入数据
if (address & 0x80) {
GPIO_SetBits(MAX7219_DIN_PORT, MAX7219_DIN_PIN);
} else {
GPIO_ResetBits(MAX7219_DIN_PORT, MAX7219_DIN_PIN);
}
// 产生时钟脉冲 1-0
GPIO_SetBits(MAX7219_CLK_PORT, MAX7219_CLK_PIN);
delay_us(1); // 延迟1微秒
GPIO_ResetBits(MAX7219_CLK_PORT, MAX7219_CLK_PIN);
delay_us(1); // 延迟1微秒
address <<= 1;
}
// 发送数据字节
for (i = 0; i < 8; i++) {
// 设置DIN引脚数据位
if (data & 0x80) {
GPIO_SetBits(MAX7219_DIN_PORT, MAX7219_DIN_PIN);
} else {
GPIO_ResetBits(MAX7219_DIN_PORT, MAX7219_DIN_PIN);
}
// 产生时钟脉冲 1-0
GPIO_SetBits(MAX7219_CLK_PORT, MAX7219_CLK_PIN);
delay_us(1); // 延迟1微秒
GPIO_ResetBits(MAX7219_CLK_PORT, MAX7219_CLK_PIN);
delay_us(1); // 延迟1微秒
data <<= 1;
}
}
// SPI1 读写一个字节
void SPI2_ReadWriteByte(uint8_t address, uint8_t data)
{
uint8_t i;
// 发送地址字节
for (i = 0; i < 8; i++) {
// 设置DIN引脚数据位-写入数据
if (address & 0x80) {
GPIO_SetBits(MAX7219_DIN2_PORT, MAX7219_DIN2_PIN);
} else {
GPIO_ResetBits(MAX7219_DIN2_PORT, MAX7219_DIN2_PIN);
}
// 产生时钟脉冲 1-0
GPIO_SetBits(MAX7219_CLK2_PORT, MAX7219_CLK2_PIN);
delay_us(1); // 延迟1微秒
GPIO_ResetBits(MAX7219_CLK2_PORT, MAX7219_CLK2_PIN);
delay_us(1); // 延迟1微秒
address <<= 1;
}
// 发送数据字节
for (i = 0; i < 8; i++) {
// 设置DIN引脚数据位
if (data & 0x80) {
GPIO_SetBits(MAX7219_DIN2_PORT, MAX7219_DIN2_PIN);
} else {
GPIO_ResetBits(MAX7219_DIN2_PORT, MAX7219_DIN2_PIN);
}
// 产生时钟脉冲 1-0
GPIO_SetBits(MAX7219_CLK2_PORT, MAX7219_CLK2_PIN);
delay_us(1); // 延迟1微秒
GPIO_ResetBits(MAX7219_CLK2_PORT, MAX7219_CLK2_PIN);
delay_us(1); // 延迟1微秒
data <<= 1;
}
}
//写数据*********************************************************************
void MAX7219_Write1(uint8_t addr, uint8_t dat)
{
MAX7219_CS1_0;
SPI1_ReadWriteByte(addr,dat);
MAX7219_CS1_1;
}
//写数据*********************************************************************
void MAX7219_Write2(uint8_t addr, uint8_t dat)
{
MAX7219_CS2_0;
SPI2_ReadWriteByte(addr,dat);
MAX7219_CS2_1;
}
/*
功能:初始化Max7219
参数:-
介绍:有需求可自己调参,一般不需改动
*/
void MAX7219_Init(void)
{
MX_SPI1_Init();
MX_SPI2_Init();
// 初始化 MAX7219
MAX7219_Write1(0x09, 0x00); // 译码方式:BCD码
MAX7219_Write1(0x0a, m_Intensity); // 亮度
MAX7219_Write1(0x0b, 0x07); // 扫描界限:8个数码管显示
MAX7219_Write1(0x0c, 0x01); // 掉电模式:0,普通模式:1
MAX7219_Write1(0x0f, 0x00); // 显示测试:1;测试结束,正常显示:0
MAX7219_Write2(0x09, 0x00); // 译码方式:BCD码
MAX7219_Write2(0x0a, m_Intensity); // 亮度
MAX7219_Write2(0x0b, 0x07); // 扫描界限:8个数码管显示
MAX7219_Write2(0x0c, 0x01); // 掉电模式:0,普通模式:1
MAX7219_Write2(0x0f, 0x00); // 显示测试:1;测试结束,正常显示:0
}
void MAX7219_IntensityAdd(void)
{
if (m_Intensity < 0x0F)
{
m_Intensity++;
MAX7219_Write1(0x0a, m_Intensity);
MAX7219_Write2(0x0a, m_Intensity);
}
}
void MAX7219_IntensityMinus(void)
{
if (m_Intensity > 0)
{
m_Intensity--;
MAX7219_Write1(0x0a, m_Intensity);
MAX7219_Write2(0x0a, m_Intensity);
}
}
void MAX7219_ShowMatrix(uint8_t M1[8][8], uint8_t M2[8][8])
{
uint8_t i, j, dat;
// 更新第一个 MAX7219
for (i = 0; i < 8; i++)
{
dat = (M1[i][0] << 7) | (M1[i][1] << 6) | (M1[i][2] << 5) | (M1[i][3] << 4) | (M1[i][4] << 3) | (M1[i][5] << 2) | (M1[i][6] << 1) | (M1[i][7] << 0);
MAX7219_Write1(i+1 , dat); // 更新第一个模块
}
// 更新第二个 MAX7219
for (i = 0; i < 8; i++)
{
dat = (M2[i][0] << 7) | (M2[i][1] << 6) | (M2[i][2] << 5) | (M2[i][3] << 4) | (M2[i][4] << 3) | (M2[i][5] << 2) | (M2[i][6] << 1) | (M2[i][7] << 0);
MAX7219_Write2(i+1 , dat); // 更新第二个模块
}
}
void MX_SPI1_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 使能GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE);
// 配置CS引脚为推挽输出模式
GPIO_InitStructure.GPIO_Pin = MAX7219_CS1_PIN ;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(MAX7219_CS1_PORT, &GPIO_InitStructure);
// 配置CLK引脚为推挽输出模式
GPIO_InitStructure.GPIO_Pin = MAX7219_CLK_PIN;
GPIO_Init(MAX7219_CLK_PORT, &GPIO_InitStructure);
// 配置DIN引脚为推挽输出模式-Mosi
GPIO_InitStructure.GPIO_Pin = MAX7219_DIN_PIN;
GPIO_Init(MAX7219_DIN_PORT, &GPIO_InitStructure);
}
void MX_SPI2_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 使能GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB , ENABLE);
// 配置CS引脚为推挽输出模式
GPIO_InitStructure.GPIO_Pin = MAX7219_CS2_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(MAX7219_CS2_PORT, &GPIO_InitStructure);
// 配置CLK引脚为推挽输出模式
GPIO_InitStructure.GPIO_Pin = MAX7219_CLK2_PIN;
GPIO_Init(MAX7219_CLK2_PORT, &GPIO_InitStructure);
// 配置DIN引脚为推挽输出模式-Mosi
GPIO_InitStructure.GPIO_Pin = MAX7219_DIN2_PIN;
GPIO_Init(MAX7219_DIN2_PORT, &GPIO_InitStructure);
}
max7219.h
这里的 IO 口也可以自定义
#ifndef __SPI_H__
#define __SPI_H__
#include "stm32f10x.h"
//模拟SPI,如有需求自己改动即可
#define MAX7219_CS1_PIN GPIO_Pin_4
#define MAX7219_CS1_PORT GPIOA
#define MAX7219_CLK_PIN GPIO_Pin_5
#define MAX7219_CLK_PORT GPIOA
#define MAX7219_DIN_PIN GPIO_Pin_6
#define MAX7219_DIN_PORT GPIOA
#define MAX7219_CS2_PIN GPIO_Pin_4
#define MAX7219_CS2_PORT GPIOB
#define MAX7219_CLK2_PIN GPIO_Pin_5
#define MAX7219_CLK2_PORT GPIOB
#define MAX7219_DIN2_PIN GPIO_Pin_6
#define MAX7219_DIN2_PORT GPIOB
#define MAX7219_CS1_0 GPIO_ResetBits(MAX7219_CS1_PORT, MAX7219_CS1_PIN)
#define MAX7219_CS1_1 GPIO_SetBits(MAX7219_CS1_PORT, MAX7219_CS1_PIN)
#define MAX7219_CS2_0 GPIO_ResetBits(MAX7219_CS2_PORT, MAX7219_CS2_PIN)
#define MAX7219_CS2_1 GPIO_SetBits(MAX7219_CS2_PORT, MAX7219_CS2_PIN)
void MAX7219_Init(void);
void MX_SPI1_Init(void);
void MX_SPI2_Init(void);
void MAX7219_ShowMatrix(uint8_t M1[8][8], uint8_t M2[8][8]);
void MAX7219_IntensityMinus(void);
void MAX7219_IntensityAdd(void);
// SPI1 读写一个字节
void SPI1_ReadWriteByte(uint8_t address, uint8_t data);
#endif /* __SPI_H__ */
3.主代码部分
main.c
实现设备的初始化和数据初始
#include "git.h"
// 软件定时器设定
static Timer task1_id;
static Timer task2_id;
static Timer task3_id;
extern u8 time25ms;
extern uint8_t m_Mat1[8][8], m_Mat2[8][8];
// 获取全局变量
const char *topics[] = {S_TOPIC_NAME};
// 硬件初始化
void Hardware_Init(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
delay_init(); // 延时函数初始化
GENERAL_TIM_Init(TIM_4, 0, 1);
Usart1_Init(115200); // 串口1初始化为115200
Usart3_Init(115200); // 串口3,驱动ESP8266用
System_PB34_setIO();
LED_Init();
Key_GPIO_Config(); // key
MAX7219_Init(); // MAX719
Matrix_DispNumberOrLetterByIdx(m_Mat1, 23);
Matrix_DispNumberOrLetterByIdx(m_Mat2, 25);
MAX7219_ShowMatrix(m_Mat1, m_Mat2);
IIC_Init();
MPU6050_initialize(); // 陀螺仪
printf("okn");
#if OLED // OLED文件存在
OLED_Init();
OLED_ColorTurn(0); // 0正常显示,1 反色显示
OLED_DisplayTurn(0); // 0正常显示 1 屏幕翻转显示
#endif
while (Reset_Threshole_Value(&threshold_value_init, &device_state_init) != MY_SUCCESSFUL)
delay_ms(5); // 初始化阈值
#if OLED // OLED文件存在
OLED_Clear();
#endif
}
// 任务1
void task1(void)
{
// 1秒计算器
Automation_Close();
}
// 任务2
void task2(void)
{
Read_Data(&Data_init); // 更新传感器数据
Update_device_massage(); // 更新设备
State = ~State;
}
// 软件初始化
void SoftWare_Init(void)
{
// 定时器初始化
timer_init(&task1_id, task1, 980, 1); // 1s 执行一次
timer_init(&task2_id, task2, 50, 1); // (git里面设置) ms执行一次
timer_start(&task1_id);
timer_start(&task2_id);
}
// 主函数
int main(void)
{
unsigned char *dataPtr = NULL;
SoftWare_Init(); // 软件初始化
Hardware_Init(); // 硬件初始化
// 启动提示
Data_init.Beep = 100;
TIM_Cmd(TIM4, ENABLE); // 使能计数器
while (1) {
// 线程
timer_loop(); // 定时器执行
#if KEY_OPEN
// 按键监测
if (time25ms == MY_TRUE) {
Check_Key_ON_OFF();
time25ms = MY_FALSE;
}
#endif
}
}
代码主要实现部分
// 更新设备状态
mySta Update_device_massage()
{
// 模式
if (m_Mode == 0)
{
Matrix_DispNumberOrLetterByIdx(m_Mat1, m_DropDelay);
Matrix_DispNumberOrLetterByIdx(m_Mat2, Data_init.Wait_Time); // T
}
else if (m_Mode == 1) // 漏沙模式
{
m_Moved = Update_Matrix();
if (m_FrameCounter >=1000/Data_init.Time *m_DropDelay || (m_DropDelay == 21 && m_FrameCounter >= 1000/Data_init.Time))
{
if (m_Dropped){
m_FrameCounter = 0;
}
}
if (m_DropDelay == 21 && m_Moved == 0 && m_Dropped == 0) // 漏沙循环
{
if( Data_init.ball_num == 60){
Data_init.ball_num =0;
}
if (Is_Matrix_Empty(m_Mat1) && m_Rotation == 0)
{
Matrix_Fill(m_Mat1);
m_Mat1[0][0] = 0;
m_Mat1[0][1] = 0;
m_Mat1[1][0] = 0;
m_Mat1[1][1] = 0;
Matrix_Clear(m_Mat2);
}
else if (Is_Matrix_Empty(m_Mat2) && m_Rotation == 180)
{
Matrix_Fill(m_Mat2);
m_Mat2[0][0] = 0;
m_Mat2[0][1] = 0;
m_Mat2[1][0] = 0;
m_Mat2[1][1] = 0;
Matrix_Clear(m_Mat1);
}
}
else if (Data_init.ball_num >= 60 && m_Moved == 0 && m_Dropped == 0 && (m_Rotation == 0 || m_Rotation == 180)) // 漏沙结束后切换到模式2
{
Data_init.Beep = 500;
m_FrameCounter = 0;
m_Mode = 2;
}
}
else if (m_Mode == 2) // 待机眨眼动画
{
if (m_FrameCounter >= 1000 / 66 * m_BlinkDelay)
{
Blink();
m_BlinkDelay = rand() % 5 + 5;
m_FrameCounter = 0;
}
else{
OpenEyes();
}
}
if(Data_init.ball_num == 60 && m_DropDelay != 21){
Data_init.Beep = 25;
}
// 循环 / 定时
if(Data_init.Flage == 1){
m_DropDelay = 21;
}else{
m_DropDelay = 23;
}
MAX7219_ShowMatrix(m_Mat1, m_Mat2);
m_FrameCounter++;
return MY_SUCCESSFUL;
}
四 程序说明
按键说明
按键有长短按模式
长按可以切换模拟,短按可以设置记时长 时长为 1 - 20 min可调
1.沙漏模式
以下图为例:上面为无穷大 表示循环沙漏模式 下面的 1 表示定时时间为1 min
2.定时模式
以下图为例:上面为爱心 表示定时沙漏模式 下面的 1 表示定时时间为1 min,时间到了之后会主动蜂鸣器报警
五 参考
Arduino程序设计(十)8×8 共阴极LED点阵显示(MAX7219)https://blog.csdn.net/weixin_44887565/article/details/132803275?ops_request_misc=%257B%2522request%255Fid%2522%253A%25223b76ef48626591536727ad003b02259d%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=3b76ef48626591536727ad003b02259d&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-132803275-null-null.142^v101^pc_search_result_base9&utm_term=max7219%E6%A8%A1%E5%9D%97&spm=1018.2226.3001.4187https://blog.csdn.net/weixin_44887565/article/details/132803275?ops_request_misc=%257B%2522request%255Fid%2522%253A%25223b76ef48626591536727ad003b02259d%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=3b76ef48626591536727ad003b02259d&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-132803275-null-null.142^v101^pc_search_result_base9&utm_term=max7219%E6%A8%A1%E5%9D%97&spm=1018.2226.3001.4187https://blog.csdn.net/weixin_44887565/article/details/132803275?ops_request_misc=%257B%2522request%255Fid%2522%253A%25223b76ef48626591536727ad003b02259d%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=3b76ef48626591536727ad003b02259d&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-132803275-null-null.142^v101^pc_search_result_base9&utm_term=max7219%E6%A8%A1%E5%9D%97&spm=1018.2226.3001.4187https://blog.csdn.net/weixin_44887565/article/details/132803275?ops_request_misc=%257B%2522request%255Fid%2522%253A%25223b76ef48626591536727ad003b02259d%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=3b76ef48626591536727ad003b02259d&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-132803275-null-null.142^v101^pc_search_result_base9&utm_term=max7219%E6%A8%A1%E5%9D%97&spm=1018.2226.3001.4187MPU-6050详解https://blog.csdn.net/qq_30150579/article/details/136151474?ops_request_misc=%257B%2522request%255Fid%2522%253A%25224cc25091c4627d650c9a33a84e1baf9c%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=4cc25091c4627d650c9a33a84e1baf9c&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-136151474-null-null.142^v101^pc_search_result_base9&utm_term=MPU-6050%E6%A8%A1%E5%9D%97&spm=1018.2226.3001.4187https://blog.csdn.net/qq_30150579/article/details/136151474?ops_request_misc=%257B%2522request%255Fid%2522%253A%25224cc25091c4627d650c9a33a84e1baf9c%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=4cc25091c4627d650c9a33a84e1baf9c&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-136151474-null-null.142^v101^pc_search_result_base9&utm_term=MPU-6050%E6%A8%A1%E5%9D%97&spm=1018.2226.3001.4187https://blog.csdn.net/qq_30150579/article/details/136151474?ops_request_misc=%257B%2522request%255Fid%2522%253A%25224cc25091c4627d650c9a33a84e1baf9c%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=4cc25091c4627d650c9a33a84e1baf9c&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-136151474-null-null.142^v101^pc_search_result_base9&utm_term=MPU-6050%E6%A8%A1%E5%9D%97&spm=1018.2226.3001.4187
https://blog.csdn.net/qq_30150579/article/details/136151474?ops_request_misc=%257B%2522request%255Fid%2522%253A%25224cc25091c4627d650c9a33a84e1baf9c%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=4cc25091c4627d650c9a33a84e1baf9c&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-136151474-null-null.142^v101^pc_search_result_base9&utm_term=MPU-6050%E6%A8%A1%E5%9D%97&spm=1018.2226.3001.4187
联系方式 微信号:13648103287