基于“EVM_MSPM0L1306开发套件”实现运动采集系统
该项目使用了EVM_MSPM0L1306开发套件,实现了运动采集系统的设计,它的主要功能为:1、接收陀螺仪、加速度计(MPU6500)的数据,进行姿态解算;2、OLED屏幕显示IMU数据、姿态角;3、开发板将姿态信息和陀螺仪的采集数据,以1Hz左右的频率发送至上位机;4、开发板姿态发生变化时,通过LED的闪烁频率、蜂鸣器的音调和报警周期进行提示;5、通过按键选择不同的运动模式(开发板平放、侧放或倒置);6 、电位计调节LED、蜂鸣器反馈强度,例如闪烁和报警频率;7、系统工作状态呼吸灯。
标签
嵌入式系统
显示
ADC
开发板
lihuahua
更新2024-07-18
南京理工大学
934

1、项目功能介绍

1.1 接收陀螺仪、加速度计(MPU6500)的数据,进行姿态解算;

1.2 OLED屏幕显示IMU数据、姿态角;

1.3 开发板将姿态信息和陀螺仪的采集数据,以1Hz左右的频率发送至上位机;

1.4开发板姿态发生变化时,通过LED的闪烁频率、蜂鸣器的音调和报警周期进行提示;

1.5 通过按键选择不同的运动模式(开发板平放、侧放或倒置);

1.6 电位计调节LED、蜂鸣器反馈强度,例如闪烁和报警频率;

1.7 系统工作状态呼吸灯;

2、设计思路

2.1 MCU通过IIC接口与MPU6500通信,采集加速度、角速度;

2.2 MCU通过SPI接口驱动OLED屏幕,显示IMU数据、姿态角;

2.3 开发板通过串口将IMU数据、姿态角发送至上位机;

2.4通过PWM驱动LED、蜂鸣器,控制其闪烁频率、蜂鸣器的音调和报警周期;

2.5 通过GPIO输入中断读取按键状态;

2.6 通过MCU内置A/D转换器,读取电位计的电压值;

2.7 通过PWM驱动呼吸灯。

3、硬件框图

image.png

4、软件流程图

image.png

 image.png

5、简单的硬件介绍:

5.1 主控芯片:

MSPM0L1306:64kB flash,4kB sram

image.png

5.2 MPU6500

通信方式:标准IIC/SPI通信协议

芯片内置16bit A/D转换器,16位数据输出

陀螺仪范围:±250,500,1000,2000°/s

加速度范围:±2,±4,±8,±16g

 

本次项目开发板默认将mpu6500、oled显示屏挂在spi_0总线上。oled显示屏只需接收M0-SPI的发送数据,DC引脚(配置成GPIO)连接到了SPI0_POCI,这将导致spi0无法分时驱动MPU6500。

image.png

故本项目采用IIC与MPU6500进行通信(需要使用飞线,连接到SDA-PA0、SCL-PA11);PA15连接到MPU6500的cs引脚上(PA15电平应设置为高,避免MPU6500认为当前为SPI通信);拨码开关1、8应拨到下面,避免对PA0、PA11电平产生影响。

image.png

采用IIC通信时,需自己将MPU6500芯片的23-SCL、24-SDA、9-AD0引脚通过4.7k电阻上拉至3.3V。

image.png

5.3 底板电位计:

MCU通过A0端口采集电阻分压值

image.png

5.4 核心板按键:

有滤波电容。按键按下时,PA1引脚电压=3V3*R11/(R11+R8),电平为低;

image.png

5.5 OLED显示:

尺寸12864,驱动芯片SSD1306,通信方式SPI

image.png

5.6 USB/TTL电路

image.png

5.7 蜂鸣器驱动电路:

NPN三极管驱动蜂鸣器,蜂鸣器并联有二极管抑制反向电动势。

由于引脚冲突,喇叭通过跳线连接到PA18

image.png

5.8 核心板RGB、LED驱动电路

核心板led用作呼吸灯,连接到PA3

image.png

底板led_7用作闪灯,通过跳线连接到PA16

image.png

6、实现的功能及图片展示

6.1 OLED屏幕显示IMU数据、姿态角;

image.png

6.2 开发板将姿态信息和陀螺仪的采集数据,以1Hz左右的频率发送至上位机;

image.png

6.4开发板姿态发生变化时,通过LED的闪烁频率、蜂鸣器的音调和报警周期进行提示;

开发板平放:z轴加速度值为1.02g

image.png

开发板竖直:y轴加速度值为1.01g

image.png

开发板侧置:x轴加速度值为0.99g

image.png

7、主要代码片段及说明

7.1 MPU6500

IIC设备地址:ADD接地,所以设备地址为0x68,若接高电平3,3v,地址为0x69。

image.png

通信速率:I2C at 400 kHz or SPI at 1MHz.,(本文频率设为800kHz)

image.png

IIC模式:时钟上升沿采集数据,下降沿准备数据

在IIC通信中,数据的发送遵循高位在先、低位在后的原则

启动标志:在时钟总线为高时,数据总线拉低;

停止标志:在时钟总线为高时,数据总线拉高。

image.png

应答位:

image.png

注意:1-7位为地址!!!

image.png

单字节、连续写:(AD:设备地址;RA:寄存器地址)

image.png

单字节、连续读:

image.png

MPU6500的IIC初始化时序:使用逻辑分析仪抓取波形

image.png

image.png

读取mpu6500数据:

image.png

陀螺仪、加速度数据精度lsb:

image.png

#include "ti_msp_dl_config.h"
#include <math.h> //Keil library
#include "uart1_dma.h"
#include "mpu6500.h"

// 定义MPU6500内部地址
//****************************************
#define SMPLRT_DIV 0x19 //陀螺仪采样率,典型值:0x07(125Hz)
#define CONFIG 0x1A //低通滤波频率,典型值:0x06(5Hz)
#define GYRO_CONFIG 0x1B //陀螺仪自检及测量范围,典型值:0x18(不自检,2000deg/s)
#define ACCEL_CONFIG 0x1C //加速计自检、测量范围及高通滤波频率,典型值:0x01(不自检,2G,5Hz)

#define ACCEL_XOUT_H 0x3B
#define ACCEL_XOUT_L 0x3C
#define ACCEL_YOUT_H 0x3D
#define ACCEL_YOUT_L 0x3E
#define ACCEL_ZOUT_H 0x3F
#define ACCEL_ZOUT_L 0x40

#define TEMP_OUT_H 0x41
#define TEMP_OUT_L 0x42

#define GYRO_XOUT_H 0x43
#define GYRO_XOUT_L 0x44
#define GYRO_YOUT_H 0x45
#define GYRO_YOUT_L 0x46
#define GYRO_ZOUT_H 0x47
#define GYRO_ZOUT_L 0x48

#define PWR_MGMT_1 0x6B //电源管理,典型值:0x00(正常启用)
#define WHO_AM_I 0x75 //IIC地址寄存器(默认数值0x68,只读)


//****************************

#define GYRO_ADDRESS 0x69 //陀螺地址
#define ACCEL_ADDRESS 0x69

unsigned char TX_DATA[4]; //显示据缓存区
unsigned char BUF[10]; //接收数据缓存区
char test=0; //IIC用到
short T_X,T_Y,T_Z; //X,Y,Z轴
struct MPU6500_DATA mpu6500_data;
uint8_t gTxPacket_IIC[2];
uint8_t gRxPacket_IIC;

//************************************
/*模拟IIC端口输出输入定义*/
//#define SCL_H IIC_GPIO_PORT -> DOUTSET31_0 = IIC_GPIO_IIC_SCL_PIN
//#define SCL_L IIC_GPIO_PORT -> DOUTCLR31_0 = IIC_GPIO_IIC_SCL_PIN
//
//#define SDA_H IIC_GPIO_PORT -> DOUTSET31_0 = IIC_GPIO_IIC_SDA_PIN
//#define SDA_L IIC_GPIO_PORT -> DOUTCLR31_0 = IIC_GPIO_IIC_SDA_PIN

//#define SCL_read ((IIC_GPIO_PORT -> DIN31_0) & IIC_GPIO_IIC_SCL_PIN)
//#define SDA_read ((IIC_GPIO_PORT -> DIN31_0) & IIC_GPIO_IIC_SDA_PIN)


//#define Delayms(m) delay_cycles(m*32000) //延时ms


/* 变量定义 ----------------------------------------------*/

/*******************************/
void DATA_printf(uint8_t *s,short temp_data)
{
if(temp_data<0){
temp_data=-temp_data;
*s='-';
}
else *s=' ';
*++s =temp_data/100+0x30;
temp_data=temp_data%100; //取余运算
*++s =temp_data/10+0x30;
temp_data=temp_data%10; //取余运算
*++s =temp_data+0x30;
}

//单字节写入*******************************************

bool Single_Write(unsigned char SlaveAddress,unsigned char REG_Address,unsigned char REG_data) //void
{
gTxPacket_IIC[0] = REG_Address;
gTxPacket_IIC[1] = REG_data;

DL_I2C_fillControllerTXFIFO(I2C_1_INST, gTxPacket_IIC, 2);

/* Wait for I2C to be Idle */
while (!(DL_I2C_getControllerStatus(I2C_1_INST) & DL_I2C_CONTROLLER_STATUS_IDLE));

/* Send the packet to the controller.
* This function will send Start + Stop automatically. */
DL_I2C_startControllerTransfer(I2C_1_INST, SlaveAddress, DL_I2C_CONTROLLER_DIRECTION_TX, 2);
//DL_I2C_startControllerTransferAdvanced(I2C_1_INST,SlaveAddress, DL_I2C_CONTROLLER_DIRECTION_TX,2,I2C_MCTR_START_ENABLE,I2C_MCTR_STOP_ENABLE,I2C_MCTR_ACK_DISABLE);


/* Poll until the Controller writes all bytes */
while(DL_I2C_getControllerStatus(I2C_1_INST) & DL_I2C_CONTROLLER_STATUS_BUSY_BUS){;}

/* Trap if there was an error */
if (DL_I2C_getControllerStatus(I2C_1_INST) & DL_I2C_CONTROLLER_STATUS_ERROR) {
/* LED will remain high if there is an error */
__BKPT(0);
}
/* Wait for I2C to be Idle */
while (!(
DL_I2C_getControllerStatus(I2C_1_INST) & DL_I2C_CONTROLLER_STATUS_IDLE))
;

return true;
}

//单字节读取*****************************************
unsigned char Single_Read(unsigned char SlaveAddress,unsigned char REG_Address){

gTxPacket_IIC[0] = REG_Address;
//uint8_t gRxPacket;

DL_I2C_fillControllerTXFIFO(I2C_1_INST, gTxPacket_IIC, 1);

/* Wait for I2C to be Idle */
while (!(DL_I2C_getControllerStatus(I2C_1_INST) & DL_I2C_CONTROLLER_STATUS_IDLE));

/* Send the packet to the controller.
* This function will send Start + Stop automatically. */
//DL_I2C_startControllerTransferAdvanced(I2C_1_INST,SlaveAddress, DL_I2C_CONTROLLER_DIRECTION_TX,1,I2C_MCTR_START_ENABLE,I2C_MCTR_STOP_DISABLE,I2C_MCTR_ACK_ENABLE);

DL_I2C_startControllerTransfer(I2C_1_INST, SlaveAddress, DL_I2C_CONTROLLER_DIRECTION_TX, 1);

/* Poll until the Controller writes all bytes */
while(DL_I2C_getControllerStatus(I2C_1_INST) & DL_I2C_CONTROLLER_STATUS_BUSY_BUS){;}

// /* Trap if there was an error */
// if (DL_I2C_getControllerStatus(I2C_1_INST) & DL_I2C_CONTROLLER_STATUS_ERROR) {
// /* LED will remain high if there is an error */
// __BKPT(0);
// }
//
/* Wait for I2C to be Idle */
while (!(DL_I2C_getControllerStatus(I2C_1_INST) & DL_I2C_CONTROLLER_STATUS_IDLE)){ };

/* Add delay between transfers */
//delay_cycles(1000);

/* Send a read request to Target */
//DL_I2C_startControllerTransferAdvanced(I2C_1_INST,SlaveAddress, DL_I2C_CONTROLLER_DIRECTION_RX,1,I2C_MCTR_START_DISABLE,I2C_MCTR_STOP_ENABLE,I2C_MCTR_ACK_ENABLE);

DL_I2C_startControllerTransfer(I2C_1_INST, SlaveAddress,DL_I2C_CONTROLLER_DIRECTION_RX, 1);

/*Receive all bytes from target. */
while (DL_I2C_isControllerRXFIFOEmpty(I2C_1_INST)){}

gRxPacket_IIC = DL_I2C_receiveControllerData(I2C_1_INST);


return gRxPacket_IIC;

}
//************************************************
void USART1_SendData(uint8_t SendData)
{
uart1_byte_tx(SendData);

}
//初始化MPU6500,根据需要请参考pdf进行修改************************
void Init_MPU6500(void)
{
/*
Single_Write(GYRO_ADDRESS,PWR_M, 0x80); //
Single_Write(GYRO_ADDRESS,SMPL, 0x07); //
Single_Write(GYRO_ADDRESS,DLPF, 0x1E); //±2000°
Single_Write(GYRO_ADDRESS,INT_C, 0x00 ); //
Single_Write(GYRO_ADDRESS,PWR_M, 0x00); //
*/
Single_Write(GYRO_ADDRESS,PWR_MGMT_1, 0x00); //解除休眠状态
Single_Write(GYRO_ADDRESS,SMPLRT_DIV, 0x07);
Single_Write(GYRO_ADDRESS,CONFIG, 0x06);
Single_Write(GYRO_ADDRESS,GYRO_CONFIG, 0x18);
Single_Write(GYRO_ADDRESS,ACCEL_CONFIG, 0x01);

//Single_Write(GYRO_ADDRESS,0x6A,0x00);//close Master Mode
}

//******读取MPU6500数据****************************************
void READ_MPU6500_ACCEL(void)
{
//short T_X,T_Y,T_Z;

//加速度量程±2g;寄存器数据int16;转换系数:1/2^15*2 = 1/16384
BUF[0]=Single_Read(ACCEL_ADDRESS,ACCEL_XOUT_L);
BUF[1]=Single_Read(ACCEL_ADDRESS,ACCEL_XOUT_H);
T_X = (BUF[1]<<8)|BUF[0];
mpu6500_data.acc_x = T_X/164; //读取计算X轴数据(放大100倍)

BUF[2]=Single_Read(ACCEL_ADDRESS,ACCEL_YOUT_L);
BUF[3]=Single_Read(ACCEL_ADDRESS,ACCEL_YOUT_H);
T_Y= (BUF[3]<<8)|BUF[2];
mpu6500_data.acc_y = T_Y/164; //读取计算Y轴数据

BUF[4]=Single_Read(ACCEL_ADDRESS,ACCEL_ZOUT_L);
BUF[5]=Single_Read(ACCEL_ADDRESS,ACCEL_ZOUT_H);
T_Z= (BUF[5]<<8)|BUF[4];
mpu6500_data.acc_z = T_Z/164; //读取计算Z轴数据
}

void READ_MPU6500_GYRO(void)
{
//short T_X,T_Y,T_Z;
//角速度量程±2000d/s;寄存器数据int16;转换系数:1/2^15*2000 = 0.061035
BUF[0]=Single_Read(GYRO_ADDRESS,GYRO_XOUT_L);
BUF[1]=Single_Read(GYRO_ADDRESS,GYRO_XOUT_H);
T_X= (BUF[1]<<8)|BUF[0];
mpu6500_data.ang_x = T_X * 0.061035; //读取计算X轴数据

BUF[2]=Single_Read(GYRO_ADDRESS,GYRO_YOUT_L);
BUF[3]=Single_Read(GYRO_ADDRESS,GYRO_YOUT_H);
T_Y= (BUF[3]<<8)|BUF[2];
mpu6500_data.ang_y = T_Y * 0.061035; //读取计算Y轴数据

BUF[4]=Single_Read(GYRO_ADDRESS,GYRO_ZOUT_L);
BUF[5]=Single_Read(GYRO_ADDRESS,GYRO_ZOUT_H);
T_Z= (BUF[5]<<8)|BUF[4];
mpu6500_data.ang_z = T_Z * 0.061035; //读取计算Z轴数据


// BUF[6]=Single_Read(GYRO_ADDRESS,TEMP_OUT_L);
// BUF[7]=Single_Read(GYRO_ADDRESS,TEMP_OUT_H);
// T_T=(BUF[7]<<8)|BUF[6];
// T_T = 35+ ((double) (T_T + 13200)) / 280;// 读取计算出温度
}



//********串口发送数据***************************************
void Send_data(uint8_t MAG,uint8_t axis)
{
uint8_t i;
USART1_SendData(MAG);
USART1_SendData(axis);
USART1_SendData(':');
for(i=0;i<4;i++) USART1_SendData(TX_DATA[i]);
USART1_SendData(' ');
USART1_SendData(' ');
}


void mpu6500_test(void)
{
//数据读取
READ_MPU6500_ACCEL(); //加速度
READ_MPU6500_GYRO(); //陀螺

}

void mpu6500_data2pc(void){

//加速度发送到上位机
DATA_printf(TX_DATA,mpu6500_data.acc_x);//转换X轴数据到数组
Send_data('G','X'); //发送X轴数
DATA_printf(TX_DATA,mpu6500_data.acc_y);//转换Y轴数据到数组
Send_data('G','Y'); //发送Y轴数
DATA_printf(TX_DATA,mpu6500_data.acc_z);//转换Z轴数据到数组
Send_data('G','Z'); //发送Z轴数

//角速度发送到上位机
DATA_printf(TX_DATA,mpu6500_data.ang_x);//转换X轴数据到数组
Send_data('A','X'); //发送X轴数
DATA_printf(TX_DATA,mpu6500_data.ang_y);//转换Y轴数据到数组
Send_data('A','Y'); //发送Y轴数
DATA_printf(TX_DATA,mpu6500_data.ang_z);//转换Z轴数据到数组
Send_data('A','Z'); //发送Z轴数
USART1_SendData(0X0D); //换行
USART1_SendData(0X0A); //回车
}

7.2 底板电位计:

MCU通过A0端口采集电阻分压值

触发adc采样的定时器配置:

image.png

选择 ID 并选择将发布的事件

image.png

 

Adc配置为序列采样:

image.png

从计时器触发到 ADC

image.png

#include "ti_msp_dl_config.h"

#include "FreeRTOS.h"
#include "queue.h"

static volatile bool gCheckADC;
uint16_t gADCResult;

extern QueueHandle_t xQueueBuffer_AD;
BaseType_t pxHigherPriorityTaskWoken = pdFALSE;


void pot_init(void){


NVIC_EnableIRQ(ADC12_0_INST_INT_IRQN);

NVIC_EnableIRQ(TIMER_0_INST_INT_IRQN);
DL_TimerG_startCounter(TIMER_0_INST);
}

void ADC12_0_INST_IRQHandler(void)
{
switch (DL_ADC12_getPendingInterrupt(ADC12_0_INST)) {
case DL_ADC12_IIDX_MEM0_RESULT_LOADED:
gCheckADC = true;

gADCResult = DL_ADC12_getMemResult(ADC12_0_INST, DL_ADC12_MEM_IDX_0);

xQueueSendToBackFromISR(xQueueBuffer_AD,&gADCResult,&pxHigherPriorityTaskWoken);


DL_ADC12_enableConversions(ADC12_0_INST);

portYIELD_FROM_ISR(pxHigherPriorityTaskWoken);
//DL_GPIO_togglePins(Demo_PORT, Demo_LED_PIN);

break;
default:
break;
}
}


//500ms定时器
void TIMER_0_INST_IRQHandler(void)
{
switch (DL_TimerG_getPendingInterrupt(TIMER_0_INST)) {
case DL_TIMERG_IIDX_ZERO:



break;
default:
break;
}
}

7.3 核心板按键:

按键按下时,PA1引脚电压=3V3*R11/(R11+R8),电平为低;

image.png

#include "key.h"

/*
*********************************************************************************************************
* 模块名称 : 独立按键驱动模块 (外部输入IO)
* 文件名称 : bsp_key.c
* 版 本 : V1.3
* 说 明 : 扫描独立按键,具有软件滤波机制,具有按键FIFO。可以检测如下事件:
* (1) 按键按下
* (2) 按键弹起
* (3) 长按键
* (4) 长按时自动连发
*********************************************************************************************************
*/


#define HARD_KEY_NUM 1// 8 /* 实体按键个数 */
#define KEY_COUNT 1//(HARD_KEY_NUM + 2) /* 8个独立建 + 2个组合按键 */

/* 依次定义GPIO */
typedef struct
{
GPIO_Regs* gpio;
uint32_t pins;
uint8_t ActiveLevel; /* 激活电平 */
}X_GPIO_T;

/* GPIO和PIN定义 */
static const X_GPIO_T s_gpio_list[HARD_KEY_NUM] = {
{keys_PORT, keys_USER_0_PIN, 0}, /* K1 */
};


/* 定义一个宏函数简化后续代码
判断GPIO引脚是否有效按下
*/
KEY_T s_tBtn[KEY_COUNT] = {0};
KEY_FIFO_T s_tKey; /* 按键FIFO变量,结构体 */

static void bsp_InitKeyVar(void);
static void bsp_InitKeyHard(void);
static void bsp_DetectKey(uint8_t i);

#define KEY_PIN_ACTIVE(id)

/*
*********************************************************************************************************
* 函 数 名: KeyPinActive
* 功能说明: 判断按键是否按下
* 形 参: 无
* 返 回 值: 返回值1 表示按下(导通),0表示未按下(释放)
*********************************************************************************************************
*/
static uint8_t KeyPinActive(uint8_t _id)
{
uint8_t level;

if (((s_gpio_list[_id].gpio->DIN31_0) & (s_gpio_list[_id].pins)) == 0){
level = 0;
}else{
level = 1;
}

if (level == s_gpio_list[_id].ActiveLevel){
return 1;
}else{
return 0;
}
}


/*
*********************************************************************************************************
* 函 数 名: IsKeyDownFunc
* 功能说明: 判断按键是否按下。单键和组合键区分。单键事件不允许有其他键按下。
* 形 参: 无
* 返 回 值: 返回值1 表示按下(导通),0表示未按下(释放)
*********************************************************************************************************
*/
static uint8_t IsKeyDownFunc(uint8_t _id)
{
/* 实体单键 */
if (_id < HARD_KEY_NUM)
{
uint8_t i;
uint8_t count = 0;
uint8_t save = 255;

/* 判断有几个键按下 */
for (i = 0; i < HARD_KEY_NUM; i++)
{
if (KeyPinActive(i))
{
count++;
save = i;
}
}

if (count == 1 && save == _id)
{
return 1; /* 只有1个键按下时才有效 */
}

return 0;
}

/* 组合键 K1K2 */
if (_id == HARD_KEY_NUM + 0)
{
if (KeyPinActive(KID_K1) && KeyPinActive(KID_K2))
{
return 1;
}
else
{
return 0;
}
}

/* 组合键 K2K3 */
if (_id == HARD_KEY_NUM + 1)
{
if (KeyPinActive(KID_K2) && KeyPinActive(KID_K3))
{
return 1;
}
else
{
return 0;
}
}

return 0;
}

/*
*********************************************************************************************************
* 函 数 名: bsp_InitKey
* 功能说明: 初始化按键. 该函数被 bsp_Init() 调用。
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void bsp_InitKey(void)
{
bsp_InitKeyVar(); /* 初始化按键变量 */
bsp_InitKeyHard(); /* 初始化按键硬件 */
}

/*
*********************************************************************************************************
* 函 数 名: bsp_InitKeyHard
* 功能说明: 配置按键对应的GPIO
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
static void bsp_InitKeyHard(void){

}

/*
*********************************************************************************************************
* 函 数 名: bsp_InitKeyVar
* 功能说明: 初始化按键变量
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
static void bsp_InitKeyVar(void)
{
uint8_t i;

/* 对按键FIFO读写指针清零 */
s_tKey.Read = 0;
s_tKey.Write = 0;
s_tKey.Read2 = 0;

/* 给每个按键结构体成员变量赋一组缺省值 */
for (i = 0; i < KEY_COUNT; i++)
{
s_tBtn[i].LongTime = KEY_LONG_TIME; /* 长按时间 0 表示不检测长按键事件 */
s_tBtn[i].Count = KEY_FILTER_TIME / 2; /* 计数器设置为滤波时间的一半 */
s_tBtn[i].State = 0; /* 按键缺省状态,0为未按下 */
s_tBtn[i].RepeatSpeed = 0; /* 按键连发的速度,0表示不支持连发 */
s_tBtn[i].RepeatCount = 0; /* 连发计数器 */
}

/* 如果需要单独更改某个按键的参数,可以在此单独重新赋值 */

// /* 摇杆上下左右,支持长按1秒后,自动连发 */
// bsp_SetKeyParam(KID_JOY_U, 100, 6);
// bsp_SetKeyParam(KID_JOY_D, 100, 6);
// bsp_SetKeyParam(KID_JOY_L, 100, 6);
// bsp_SetKeyParam(KID_JOY_R, 100, 6);
}

/*
*********************************************************************************************************
* 函 数 名: bsp_PutKey
* 功能说明: 将1个键值压入按键FIFO缓冲区。可用于模拟一个按键。
* 形 参: _KeyCode : 按键代码
* 返 回 值: 无
*********************************************************************************************************
*/
void bsp_PutKey(uint8_t _KeyCode)
{
s_tKey.Buf[s_tKey.Write] = _KeyCode;

if (++s_tKey.Write >= KEY_FIFO_SIZE)
{
s_tKey.Write = 0;
}
}

/*
*********************************************************************************************************
* 函 数 名: bsp_GetKey
* 功能说明: 从按键FIFO缓冲区读取一个键值。
* 形 参: 无
* 返 回 值: 按键代码
*********************************************************************************************************
*/
uint8_t bsp_GetKey(void)
{
uint8_t ret;

if (s_tKey.Read == s_tKey.Write){

return KEY_NONE;

}else{

ret = s_tKey.Buf[s_tKey.Read];

if (++s_tKey.Read >= KEY_FIFO_SIZE)
{
s_tKey.Read = 0;
}
return ret;
}
}

/*
*********************************************************************************************************
* 函 数 名: bsp_GetKey2
* 功能说明: 从按键FIFO缓冲区读取一个键值。独立的读指针。
* 形 参: 无
* 返 回 值: 按键代码
*********************************************************************************************************
*/
uint8_t bsp_GetKey2(void)
{
uint8_t ret;

if (s_tKey.Read2 == s_tKey.Write)
{
return KEY_NONE;
}
else
{
ret = s_tKey.Buf[s_tKey.Read2];

if (++s_tKey.Read2 >= KEY_FIFO_SIZE)
{
s_tKey.Read2 = 0;
}
return ret;
}
}

/*
*********************************************************************************************************
* 函 数 名: bsp_GetKeyState
* 功能说明: 读取按键的状态
* 形 参: _ucKeyID : 按键ID,从0开始
* 返 回 值: 1 表示按下, 0 表示未按下
*********************************************************************************************************
*/
uint8_t bsp_GetKeyState(KEY_ID_E _ucKeyID)
{
return s_tBtn[_ucKeyID].State;
}

/*
*********************************************************************************************************
* 函 数 名: bsp_SetKeyParam
* 功能说明: 设置按键参数
* 形 参:_ucKeyID : 按键ID,从0开始
* _LongTime : 长按事件时间
* _RepeatSpeed : 连发速度
* 返 回 值: 无
*********************************************************************************************************
*/
void bsp_SetKeyParam(uint8_t _ucKeyID, uint16_t _LongTime, uint8_t _RepeatSpeed)
{
s_tBtn[_ucKeyID].LongTime = _LongTime; /* 长按时间 0 表示不检测长按键事件 */
s_tBtn[_ucKeyID].RepeatSpeed = _RepeatSpeed; /* 按键连发的速度,0表示不支持连发 */
s_tBtn[_ucKeyID].RepeatCount = 0; /* 连发计数器 */
}

/*
*********************************************************************************************************
* 函 数 名: bsp_ClearKey
* 功能说明: 清空按键FIFO缓冲区
* 形 参:无
* 返 回 值: 按键代码
*********************************************************************************************************
*/
void bsp_ClearKey(void)
{
s_tKey.Read = s_tKey.Write;
}

/*
*********************************************************************************************************
* 函 数 名: bsp_DetectKey
* 功能说明: 检测一个按键。非阻塞状态,必须被周期性的调用。
* 形 参: IO的id, 从0开始编码
* 返 回 值: 无
*********************************************************************************************************
*/
static void bsp_DetectKey(uint8_t i)
{
KEY_T *pBtn;

pBtn = &s_tBtn[i];

if(IsKeyDownFunc(i)){//按键按下

if(pBtn->Count < KEY_FILTER_TIME){
pBtn->Count = KEY_FILTER_TIME;
}else if(pBtn->Count < 2 * KEY_FILTER_TIME){//滤波时间
pBtn->Count++;
}else{

if(pBtn->State == 0){
pBtn->State = 1;

bsp_PutKey((uint8_t)(3 * i + 1));/* 发送按钮按下的消息 */
}

if(pBtn->LongTime > 0){//如果开启了长按功能

if(pBtn->LongCount < pBtn->LongTime){

if(++pBtn->LongCount == pBtn->LongTime){/* 发送按钮持续按下的消息 */

bsp_PutKey((uint8_t)(3 * i + 3));/* 键值放入按键FIFO */
}

}else{

if (pBtn->RepeatSpeed > 0){//如果开启了连续按键功能
if(++pBtn->RepeatCount >= pBtn->RepeatSpeed){
pBtn->RepeatCount = 0;

bsp_PutKey((uint8_t)(3 * i + 1));/* 常按键后,每隔10ms发送1个按键 */
}
}

}
}
}

}else{//按键没有按下

if(pBtn->Count > KEY_FILTER_TIME){
pBtn->Count = KEY_FILTER_TIME;
}else if(pBtn->Count != 0){ //滤波时间
pBtn->Count--;
}else{

if (pBtn->State == 1){
pBtn->State = 0;
bsp_PutKey((uint8_t)(3 * i + 2));/* 发送按钮弹起的消息 */
}
}

pBtn->LongCount = 0;
pBtn->RepeatCount = 0;
}
}

/*
*********************************************************************************************************
* 函 数 名: bsp_DetectFastIO
* 功能说明: 检测高速的输入IO. 1ms刷新一次
* 形 参: IO的id, 从0开始编码
* 返 回 值: 无
*********************************************************************************************************
*/
static void bsp_DetectFastIO(uint8_t i)
{
KEY_T *pBtn;

pBtn = &s_tBtn[i];

if(IsKeyDownFunc(i)){

if (pBtn->State == 0){

pBtn->State = 1;

/* 发送按钮按下的消息 */
bsp_PutKey((uint8_t)(3 * i + 1));
}

if (pBtn->LongTime > 0)
{
if (pBtn->LongCount < pBtn->LongTime)
{
/* 发送按钮持续按下的消息 */
if (++pBtn->LongCount == pBtn->LongTime)
{
/* 键值放入按键FIFO */
bsp_PutKey((uint8_t)(3 * i + 3));
}
}
else
{
if (pBtn->RepeatSpeed > 0)
{
if (++pBtn->RepeatCount >= pBtn->RepeatSpeed)
{
pBtn->RepeatCount = 0;
/* 常按键后,每隔10ms发送1个按键 */
bsp_PutKey((uint8_t)(3 * i + 1));
}
}
}
}
}
else
{
if (pBtn->State == 1)
{
pBtn->State = 0;

/* 发送按钮弹起的消息 */
bsp_PutKey((uint8_t)(3 * i + 2));
}

pBtn->LongCount = 0;
pBtn->RepeatCount = 0;
}
}

/*
*********************************************************************************************************
* 函 数 名: bsp_KeyScan10ms
* 功能说明: 扫描所有按键。非阻塞,被systick中断周期性的调用,10ms一次
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void bsp_KeyScan10ms(void)
{
uint8_t i;

for (i = 0; i < KEY_COUNT; i++)
{
bsp_DetectKey(i);
}
}

/*
*********************************************************************************************************
* 函 数 名: bsp_KeyScan1ms
* 功能说明: 扫描所有按键。非阻塞,被systick中断周期性的调用,1ms一次.
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void bsp_KeyScan1ms(void)
{
uint8_t i;

for (i = 0; i < KEY_COUNT; i++)
{
bsp_DetectFastIO(i);
}
}

7.4 OLED显示:

尺寸12864,驱动芯片SSD1306,通信方式SPI

image.png

image.pngimage.png

#include "oled_spi.h"
#include "oledfont.h"

// 向SSD1306写入一个字节。
// dat:要写入的数据/命令
// cmd:数据/命令标志 0,表示命令;1,表示数据;
void OLED_WR_Byte(u8 dat, u8 cmd)
{

if (cmd)
{
OLED_DC_Set();
}
else
{
OLED_DC_Clr();
}

while (DL_SPI_isBusy(SPI_0_INST))
{
}
DL_SPI_transmitData8(SPI_0_INST, dat);
while (DL_SPI_isBusy(SPI_0_INST))
{
}

OLED_DC_Set();
}

void OLED_Set_Pos(unsigned char x, unsigned char y)
{
OLED_WR_Byte(0xb0 + y, OLED_CMD);
OLED_WR_Byte(((x & 0xf0) >> 4) | 0x10, OLED_CMD);
OLED_WR_Byte((x & 0x0f) | 0x01, OLED_CMD);
}
// 开启OLED显示
void OLED_Display_On(void)
{
OLED_WR_Byte(0X8D, OLED_CMD); // SET DCDC命令
OLED_WR_Byte(0X14, OLED_CMD); // DCDC ON
OLED_WR_Byte(0XAF, OLED_CMD); // DISPLAY ON
}
// 关闭OLED显示
void OLED_Display_Off(void)
{
OLED_WR_Byte(0X8D, OLED_CMD); // SET DCDC命令
OLED_WR_Byte(0X10, OLED_CMD); // DCDC OFF
OLED_WR_Byte(0XAE, OLED_CMD); // DISPLAY OFF
}
// 清屏函数,清完屏,整个屏幕是黑色的!和没点亮一样!!!
void OLED_Clear(void)
{
u8 i, n;
for (i = 0; i < 8; i++)
{
OLED_WR_Byte(0xb0 + i, OLED_CMD); // 设置页地址(0~7)
OLED_WR_Byte(0x02, OLED_CMD); // 设置显示位置—列低地址
OLED_WR_Byte(0x10, OLED_CMD); // 设置显示位置—列高地址
for (n = 0; n < 128; n++)
OLED_WR_Byte(0, OLED_DATA);
} // 更新显示
}

// 在指定位置显示一个字符,包括部分字符
// x:0~127
// y:0~7 (页地址)
void OLED_ShowChar(u8 x, u8 y, char chr)
{
unsigned char c = 0, i = 0;
c = chr - ' '; // 得到偏移后的值
if (x > Max_Column - 1)
{
x = 0;
y = y + 2;
}
if (SIZE == 16)
{
OLED_Set_Pos(x, y);
for (i = 0; i < 8; i++)
OLED_WR_Byte(F8X16[c * 16 + i], OLED_DATA);
OLED_Set_Pos(x, y + 1);
for (i = 0; i < 8; i++)
OLED_WR_Byte(F8X16[c * 16 + i + 8], OLED_DATA);
}
else
{
OLED_Set_Pos(x, y + 1);
for (i = 0; i < 6; i++)
OLED_WR_Byte(F6x8[c][i], OLED_DATA);
}
}
// m^n函数
u32 oled_pow(u8 m, u8 n)
{
u32 result = 1;
while (n--)
result *= m;
return result;
}

//显示正负数字
void OLED_ShowNum_int(u8 x, u8 y, int num, u8 len, u8 size2)
{
if( num >= 0){
OLED_ShowNum(x+8, y, num, len, size2);
OLED_ShowChar(x, y, ' ');
}else{
OLED_ShowNum(x+8, y, -num, len, size2);
OLED_ShowChar(x, y, '-');
}
}


// 显示数字
// x,y :起点坐标
// len :数字的位数
// size2:字体大小
// num:数值(0~4294967295);
void OLED_ShowNum(u8 x, u8 y, u32 num, u8 len, u8 size2)
{
u8 t, temp;
u8 enshow = 0;
for (t = 0; t < len; t++)
{
temp = (num / oled_pow(10, len - t - 1)) % 10;
if (enshow == 0 && t < (len - 1))
{
if (temp == 0)
{
OLED_ShowChar(x + (size2 / 2) * t, y, ' ');
continue;
}
else
enshow = 1;
}
OLED_ShowChar(x + (size2 / 2) * t, y, temp + '0');
}
}
// 显示一个字符号串
void OLED_ShowString(u8 x, u8 y, char *chr)
{
unsigned char j = 0;
while (chr[j] != '\0')
{
OLED_ShowChar(x, y, chr[j]);
x += 8;
if (x > 120)
{
x = 0;
y += 2;
}
j++;
}
}
// 显示汉字
void OLED_ShowCHinese(u8 x, u8 y, u8 no)
{
u8 t, adder = 0;
OLED_Set_Pos(x, y);
for (t = 0; t < 16; t++)
{
OLED_WR_Byte(Hzk[2 * no][t], OLED_DATA);
adder += 1;
}
OLED_Set_Pos(x, y + 1);
for (t = 0; t < 16; t++)
{
OLED_WR_Byte(Hzk[2 * no + 1][t], OLED_DATA);
adder += 1;
}
}
/***********功能描述:显示显示BMP图片128×64起始点坐标(x,y),x的范围0~127,y为需要使用的页的范围1~8*****************/
void OLED_DrawBMP(unsigned char x0, unsigned char y0, unsigned char x1, unsigned char y1, const unsigned char BMP[])
{
unsigned int j = 0;
unsigned char x, y;

if (y1 % 8 == 0)
y = y1 / 8;
else
y = y1 / 8 + 1;
for (y = y0; y < y1; y++)
{
OLED_Set_Pos(x0, y);
for (x = x0; x < x1; x++)
{
OLED_WR_Byte(BMP[j++], OLED_DATA);
}
}
}

// 初始化SSD1306
void OLED_Init(void)
{
OLED_WR_Byte(0xAE, OLED_CMD); //--turn off oled panel
OLED_WR_Byte(0x02, OLED_CMD); //---set low column address
OLED_WR_Byte(0x10, OLED_CMD); //---set high column address
OLED_WR_Byte(0x40, OLED_CMD); //--set start line address Set Mapping RAM Display Start Line (0x00~0x3F)
OLED_WR_Byte(0x81, OLED_CMD); //--set contrast control register
OLED_WR_Byte(0xCF, OLED_CMD); // Set SEG Output Current Brightness
OLED_WR_Byte(0xA1, OLED_CMD); //--Set SEG/Column Mapping 0xa0左右反置 0xa1正常
OLED_WR_Byte(0xC8, OLED_CMD); // Set COM/Row Scan Direction 0xc0上下反置 0xc8正常
OLED_WR_Byte(0xA6, OLED_CMD); //--set normal display
OLED_WR_Byte(0xA8, OLED_CMD); //--set multiplex ratio(1 to 64)
OLED_WR_Byte(0x3f, OLED_CMD); //--1/64 duty
OLED_WR_Byte(0xD3, OLED_CMD); //-set display offset Shift Mapping RAM Counter (0x00~0x3F)
OLED_WR_Byte(0x00, OLED_CMD); //-not offset
OLED_WR_Byte(0xd5, OLED_CMD); //--set display clock divide ratio/oscillator frequency
OLED_WR_Byte(0x80, OLED_CMD); //--set divide ratio, Set Clock as 100 Frames/Sec
OLED_WR_Byte(0xD9, OLED_CMD); //--set pre-charge period
OLED_WR_Byte(0xF1, OLED_CMD); // Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
OLED_WR_Byte(0xDA, OLED_CMD); //--set com pins hardware configuration
OLED_WR_Byte(0x12, OLED_CMD);
OLED_WR_Byte(0xDB, OLED_CMD); //--set vcomh
OLED_WR_Byte(0x40, OLED_CMD); // Set VCOM Deselect Level
OLED_WR_Byte(0x20, OLED_CMD); //-Set Page Addressing Mode (0x00/0x01/0x02)
OLED_WR_Byte(0x02, OLED_CMD); //
OLED_WR_Byte(0x8D, OLED_CMD); //--set Charge Pump enable/disable
OLED_WR_Byte(0x14, OLED_CMD); //--set(0x10) disable
OLED_WR_Byte(0xA4, OLED_CMD); // Disable Entire Display On (0xa4/0xa5)
OLED_WR_Byte(0xA6, OLED_CMD); // Disable Inverse Display On (0xa6/a7)
OLED_WR_Byte(0xAF, OLED_CMD); //--turn on oled panel

OLED_WR_Byte(0xAF, OLED_CMD); /*display ON*/
OLED_Clear();
OLED_Set_Pos(0, 0);
}


7.5 USB/TTL电路

image.png

使能dma发送

image.png

#include "ti_msp_dl_config.h"
#include "uart1_dma.h"

/* Data for UART to transmit */
const uint8_t gTxPacket[] = {1, 2, 3, 4};

volatile bool gCheckUART, gDMADone;

void uart1_dma_addr_init(void){

/* Configure DMA source, destination and size */
DL_DMA_setSrcAddr(DMA, DMA_CH0_CHAN_ID, (uint32_t) &gTxPacket[0]);
DL_DMA_setDestAddr(DMA, DMA_CH0_CHAN_ID, (uint32_t)(&UART_1_INST->TXDATA));
NVIC_EnableIRQ(UART_1_INST_INT_IRQN); //中断使能
}

void uart1_dma_enable(uint8_t len){

//发送长度
DL_DMA_setTransferSize( DMA, DMA_CH0_CHAN_ID, len);

/*The UART DMA TX interrupt is set indicating the UART is ready to transmit data, so enabling the DMA will start the transfer */
DL_DMA_enableChannel(DMA, DMA_CH0_CHAN_ID);
}

void UART_1_INST_IRQHandler(void)
{
switch (DL_UART_Main_getPendingInterrupt(UART_1_INST)) {
case DL_UART_MAIN_IIDX_EOT_DONE: //串口数据发送完成
gCheckUART = true;
break;
case DL_UART_MAIN_IIDX_DMA_DONE_TX: //dma数据传送完成
gDMADone = true;
break;
default:
break;
}
}


//单字节数据发送
void uart1_byte_tx(uint8_t txData){

DL_UART_Main_transmitData(UART_1_INST, txData);
/* Wait until all bytes have been transmitted and the TX FIFO is empty */
while (DL_UART_Main_isBusy(UART_1_INST))
;
}


 

7.6 蜂鸣器驱动电路:

NPN三极管驱动蜂鸣器,蜂鸣器并联有二极管抑制反向电动势。

image.png

image.png

image.png

音调对应周期数:

1、125

2、111

3、99

4、94

5、84

6、74

7、66

 #include "passive_buzzer.h"
#include "ti_msp_dl_config.h"


struct tone tone_example[7] = {
{125,62}, //do
{111,55}, //re
{99,49}, //mi
{94,47}, //fa
{84,42}, //so
{74,37}, //la
{66,33} //si
};


//功能描述: 无源蜂鸣器控制函数
//输入参数: on - 1-响, 0-不响
void PassiveBuzzer_Control(int on)
{
if(on)
{
//启动pwm
DL_TimerG_startCounter(PWM_buzz_INST);
}
else
{
//暂停pwm
DL_TimerG_stopCounter(PWM_buzz_INST);
}
}

static DL_TimerG_PWMConfig gPWM_buzzConfig = {
.pwmMode = DL_TIMER_PWM_MODE_EDGE_ALIGN,
.period = 1000,
.startTimer = DL_TIMER_STOP,
};

//功能描述: 无源蜂鸣器控制函数: 设置频率和占空比
//输入参数: freq - 频率, duty - 占空比(始终为50%)
void PassiveBuzzer_Set_Freq_Duty(int freq, int duty)
{
//暂停pwm
DL_TimerG_stopCounter(PWM_buzz_INST);

//改变周期
gPWM_buzzConfig.period = freq;
DL_TimerG_initPWMMode(PWM_buzz_INST, (DL_TimerG_PWMConfig *) &gPWM_buzzConfig);

//改变占空比
DL_TimerG_setCaptureCompareValue(PWM_buzz_INST, duty, DL_TIMERG_CAPTURE_COMPARE_0_INDEX);

DL_TimerG_startCounter(PWM_buzz_INST);
}

//此函数10ms调用一次
//Period: 多少个100ms
void PassiveBuzzer_Set_Period_Tone(uint16_t Period, unsigned char Tone){

static uint16_t i = 0;
if(i < Period /2)
PassiveBuzzer_Set_Freq_Duty(tone_example[Tone].freq, tone_example[Tone].duty);
else
PassiveBuzzer_Control(0);

i++;
if(i > Period) i = 0;
}


7.7 核心板LED,配置为呼吸灯

Pwm时钟源4Mhz,pwm周期=4Mhz/256 = 15625hz

image.png

image.png

#include "ti_msp_dl_config.h"
#include "breath_led.h"


#define CLK_HZ 32e+06 // 系统时钟

void breath_led_ctrl(void){
// Timer和PWM的计数均在syscfg中已启动
// Timer为256Hz;PWM为15625Hz,一周期计数值为256
// 实测发现Edge-aligned Down Counting中设置比较值为256,得到的占空比为100%,而不是syscfg中计算出的0%
// 这样需要对比较值进行重新排序:256,0,1,2,...,254,255
// 因此使用Edge-aligned Up Counting,并Invert Channel(因为引脚低电平时,led亮)

static uint8_t i = 0;
i = (i + 1) % 256; // 只设置i为0-255,不设置256,因为定时器频率为256Hz。

// 占空比三角波变化
static uint8_t direction_up = 0; // 变亮1s,变暗1s,呼吸灯周期为2s
if (i == 0)
{
direction_up = !direction_up;
}

if (direction_up) // 变亮,从0-255
{
DL_TimerG_setCaptureCompareValue(breath_led_INST, i, DL_TIMERG_CAPTURE_COMPARE_0_INDEX);
}
else // 变暗,从256-1
{
DL_TimerG_setCaptureCompareValue(breath_led_INST, 256 - i, DL_TIMERG_CAPTURE_COMPARE_0_INDEX);
}

// 占空比正弦波变化
// 正弦波表长度为256,呼吸灯周期为1s
// DL_TimerG_setCaptureCompareValue(PWM_0_INST, sin_tab[i], DL_TIMERG_CAPTURE_COMPARE_0_INDEX);

}

static DL_TimerG_PWMConfig gblink_ledConfig = {
.pwmMode = DL_TIMER_PWM_MODE_EDGE_ALIGN_UP,
.period = 2000,
.startTimer = DL_TIMER_START,
};

//闪灯频率控制
//500-2000
void BlinkLed_Set_Freq_Duty(int freq,int cyc)
{
//暂停pwm
DL_TimerG_stopCounter(blink_led_INST);

//改变周期
gblink_ledConfig.period = freq;
DL_TimerG_initPWMMode(blink_led_INST, (DL_TimerG_PWMConfig *) &gblink_ledConfig);

//改变占空比
DL_TimerG_setCaptureCompareValue(blink_led_INST, cyc, DL_TIMERG_CAPTURE_COMPARE_0_INDEX);

DL_TimerG_startCounter(blink_led_INST);
}

底板闪灯:

image.png

7.8 应用层代码

/**
* @file freertos.c
* @author
* @brief
* @version 0.1
* @date 2024-01-14
*
* @copyright Copyright (c) 2024
*
*/


#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"
#include "queue.h"

#include "ti_msp_dl_config.h"

#include "uart1_dma.h"
#include "oled_spi.h"
#include "breath_led.h"
#include "key.h"
#include "mpu6500.h"
#include "pot.h"
#include "passive_buzzer.h"
#include "math.h"


/* Task Handles */
TaskHandle_t defaultTaskHandle;
TaskHandle_t KeyPot_TaskHandle;
TaskHandle_t OLEDTaskHandle;
TaskHandle_t MPU6500TaskHandle;
TaskHandle_t LedBuzzTaskHandle;
/* Timer Handles */

/* Semaphores */


/* Queue */
QueueHandle_t xQueueBuffer_Key;
QueueHandle_t xQueueBuffer_AD;
QueueHandle_t xQueueBuffer_mpu6500;

/* Task Definition */
void StartDefaultTask(void * argument);
void KeyPot_Task(void * argument);
void OLEDTask(void * argument);
void MPU6500Task(void * argument);
void LedBuzzTask(void * argument);

#if 0
#include <stdio.h>//打印所有任务的栈信息

//打印所有任务的栈信息
static signed char pcWriteBuffer[200];
void vApplicationIdleHook( void )
{
int i;
vTaskList(pcWriteBuffer);
for (i = 0; i < 16; i++) printf("-");
printf("\n\r");
printf("%s\n\r", pcWriteBuffer);
}

int fputc( int ch, FILE *f ){
uint8_t temp[1]={ch};
uart1_byte_tx(temp[0]);
return 0;
}
#endif

//volatile uint8_t Task_index = 8;

void FreeRTOS_Init(void)
{
xQueueBuffer_Key = xQueueCreate(10, sizeof(uint8_t));
xQueueBuffer_AD = xQueueCreate(2, sizeof(uint16_t));
xQueueBuffer_mpu6500 = xQueueCreate(2, sizeof(struct MPU6500_DATA)/2);

//任务栈是从FreeRTOSConfig.h文件中定义的HEAP空间申请
xTaskCreate(StartDefaultTask, "defaultTask", 45, NULL, tskIDLE_PRIORITY, &defaultTaskHandle);
xTaskCreate(KeyPot_Task, "KeyPot_Task", 55, NULL, 4, &KeyPot_TaskHandle);
xTaskCreate(MPU6500Task, "MPU6500Task", 90, NULL, 5, &MPU6500TaskHandle);
xTaskCreate(LedBuzzTask, "LedBuzzTask", 80, NULL, 2, &LedBuzzTaskHandle);
xTaskCreate(OLEDTask, "OLEDTask", 70, NULL, 3, &OLEDTaskHandle);
}

//默认任务:呼吸灯
void StartDefaultTask(void * argument)
{
for(;;)
{
vTaskDelay(5);
breath_led_ctrl();//设置呼吸灯pwm占空比
//Task_index = 0;

}
}


//按键扫描、电位器电压采集
void KeyPot_Task(void * argument)
{
bsp_InitKey();
pot_init();
uint8_t key_value;
BaseType_t err;
uint8_t key_num = 0;
for(;;)
{
vTaskDelay(10); //延迟10ms

bsp_KeyScan10ms(); //按键扫描

key_value = bsp_GetKey(); //读取按键值

if(key_value == KEY_1_UP){
key_num++;
if(key_num == 4) key_num = 0;
OLED_ShowNum(40, 1, key_num, 1, 14);
err = xQueueSend(xQueueBuffer_Key,&key_num,0);//将按键值写入队列
}
//Task_index = 4;

}
}


//采集加速度、角速度,通过串口发送出去
void MPU6500Task(void * argument)
{
BaseType_t err;

Init_MPU6500(); //初始化MPU6500
unsigned char i = 0;
for(;;)
{
vTaskDelay(100); //100ms采集一次
mpu6500_test();
if(i == 10){
mpu6500_data2pc(); //将陀螺仪数据发送到上位机,周期1s
i = 0;
}else{
i++;
}

err = xQueueSend(xQueueBuffer_mpu6500,&mpu6500_data,0);//将mpu6500加速度数据写入队列

OLED_ShowNum_int(32, 3, mpu6500_data.acc_x, 3, 14);
OLED_ShowNum_int(64, 3, mpu6500_data.acc_y, 3, 14);
OLED_ShowNum_int(96, 3, mpu6500_data.acc_z, 3, 14);

OLED_ShowNum_int(32, 4, mpu6500_data.ang_x, 3, 14);
OLED_ShowNum_int(64, 4, mpu6500_data.ang_y, 3, 14);
OLED_ShowNum_int(96, 4, mpu6500_data.ang_z, 3, 14);



//Task_index = 5;
}
}

#define my_abs(x) ((x>=0)?x:(-x))
//led闪烁频率、蜂鸣器的音调和报警周期
void LedBuzzTask(void * argument)
{
int led_cyc; //0-2000
uint16_t gADCResult;
BaseType_t err;
PassiveBuzzer_Control(0);//关无源蜂鸣器
uint8_t key_num = 0;
unsigned short Period;
unsigned char Tone;//音调索引值

uint8_t led_ctrl_i = 0;
short acc[3];
for(;;)
{
vTaskDelay(100); //100ms运行一次

//ad采集电压范围:0-3720
err = xQueueReceive( xQueueBuffer_AD, &gADCResult, 0);
OLED_ShowNum(80, 1, gADCResult, 4, 14);

err = xQueueReceive( xQueueBuffer_Key, &key_num, 0);
err = xQueueReceive( xQueueBuffer_mpu6500, &acc[0], 0);

if(key_num>0){

if(gADCResult < 1500){
Tone = 4;
Period = 10; //起始1s
led_cyc = 500;
}else if(gADCResult < 2500){
Tone = 3;
Period = 20;
led_cyc = 1000;
}else{
Tone = 1;
Period = 30;
led_cyc = 1500;
}

if(my_abs(acc[key_num-1]) <= 30){
;

}else if(my_abs(acc[key_num-1]) <= 65){
Tone = Tone + 1;
Period = Period - 3;
led_cyc = led_cyc - 150;
}else{
Tone = Tone + 2;
Period = Period - 6;
led_cyc = led_cyc - 300;
}

PassiveBuzzer_Set_Period_Tone(Period,Tone);//无源蜂鸣器控制函数

//1.3s驱动一次blink_led
if(led_ctrl_i > 14){
BlinkLed_Set_Freq_Duty(led_cyc,led_cyc*0.75);//闪灯频率控制
led_ctrl_i = 0;
}else{
led_ctrl_i++;
}

}else{
PassiveBuzzer_Control(0);//关无源蜂鸣器
DL_TimerG_stopCounter(blink_led_INST);//BlinkLed_Set_Freq_Duty(100,100);//闪灯频率控制
}

//Task_index = 2;

}
}

//x:0~127;y:0~7 (页地址);字体:6*8
//OLED屏幕,显示IMU数据
void OLEDTask(void * argument)
{
OLED_Init(); //初始化
OLED_Clear();

OLED_ShowString(0, 0, "M0L1306-Task-3");//标题

OLED_ShowString(0, 1, "Mode:"); //按键模式
OLED_ShowNum(40, 1, 0, 1, 14);

OLED_ShowString(56, 1, "AD:"); //电位器电压
OLED_ShowNum(80, 1, 0, 4, 14);

// OLED_ShowString(0, 2, "Duty:"); //buzzer占空比
// OLED_ShowString(72, 2, "Cyc:"); //buzzer周期

OLED_ShowString(0, 3, "Acc:"); //加速度 g
OLED_ShowString(0, 4, "Ang:"); //角加速度 °/s

OLED_ShowString(0, 6, "Time:"); //系统运行时长

//OLED_DrawBMP(0, 0, 62, 8, BMP_test);
uint16_t num = 0;
for(;;)
{
vTaskDelay(1000);

OLED_ShowNum(40, 6, num++, 6, 14);

//Task_index = 3;
}
}


#if (configCHECK_FOR_STACK_OVERFLOW)
/*
* ======== vApplicationStackOverflowHook ========
* When stack overflow checking is enabled the application must provide a
* stack overflow hook function. This default hook function is declared as
* weak, and will be used by default, unless the application specifically
* provides its own hook function.
*/
#if defined(__IAR_SYSTEMS_ICC__)
__weak void vApplicationStackOverflowHook(TaskHandle_t pxTask, char *pcTaskName)
#elif (defined(__TI_COMPILER_VERSION__))
#pragma WEAK(vApplicationStackOverflowHook)
void vApplicationStackOverflowHook(TaskHandle_t pxTask, char *pcTaskName)
#elif (defined(__GNUC__) || defined(__ti_version__))
void __attribute__((weak)) vApplicationStackOverflowHook(TaskHandle_t pxTask, char *pcTaskName)
#endif
{
/* default to spin upon stack overflow */
while (1) {}
}
#endif

8、遇到的主要难题及解决方法

  1. 在ccs12环境下无法使用DAP调试器,故改用MDK+sysconfig开发方式
  2. MDK+sysconfig开发环境搭建遇到困难:使用例程模板作为基础框架
  3. 理解mdk中build前命令:cmd.exe /C "$P../syscfg.bat '$P' project.syscfg";cmd.exe /C "$P../move_file.bat '$P"
  4. 通过b站学习使用sysconfig,效率大增
  5. 由于该芯片ram资源只有4kb,所以使用freertos经常存在内存不够现象,故需要合理分配任务栈空间

9、未来的计划

进一步学习freertos内部机制,优化该项目资源空间。

10、资料连接:

https://gitee.com/pansamic/MSPM0-DevKit#https://pansamic.gitee.io/mspm0-devkit/#/Development_Env/Keil

附件下载
Project_3.zip
程序源码
团队介绍
个人参赛
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号