本帖最后由 hulai123456 于 2023-8-30 20:06 编辑
#申请原创# #技术资源# 【HC32L96PCTA测评】+UART+IIC+ADXL345实现简单的姿态检测
UART简介
通用UART模块支持以下基本功能
■ 全双工传输、半双工传输、单线半双工传输
■ 可编程串行通信功能
两种字符长度:8比特、 9比特
三种校验方式:无检验、奇校验、偶校验
三种停止长度:1比特、 2比特、 1.5比特
■ 16比特波特率发生器
■ 多机通讯
■ 硬件地址识别
■ 硬件流控
■ DMAC硬件传输握手
下图为其结构框图
我们需要对其进行初始化和一点点配置,实现printf打印功能,我们在这里将其配置成:
波特率:9600
停止位:1
校验位:None
数据位:8
void Uart0_Init(void)
{
Uart0_GPIO_Init();
Uart0_Config(Value9600);
}
// UART0 GPIO 初始化
void Uart0_GPIO_Init(void)
{
stc_gpio_cfg_t stcGpioCfg;
DDL_ZERO_STRUCT(stcGpioCfg);
Sysctrl_SetPeripheralGate(SysctrlPeripheralGpio,TRUE);//开启GPIO外设时钟
//TX
stcGpioCfg.enDir = GpioDirOut;
Gpio_Init(UART0_TX_PORT, UART0_TX_PIN, &stcGpioCfg);
Gpio_SetAfMode(UART0_TX_PORT, UART0_TX_PIN, GpioAf2);
//RX
stcGpioCfg.enDir = GpioDirIn;
Gpio_Init(UART0_RX_PORT, UART0_RX_PIN, &stcGpioCfg);
Gpio_SetAfMode(UART0_RX_PORT, UART0_RX_PIN, GpioAf2);
}
//Uart0 配置
void Uart0_Config(uint32_t BaudRate)
{
stc_uart_cfg_t stcUartCfg;
DDL_ZERO_STRUCT(stcUartCfg);
// 先禁止接收中断
EnableNvic(UART0_2_IRQn, IrqLevel3, FALSE);
Uart_DisableIrq(M0P_UART0, UartRxIrq);
Sysctrl_SetPeripheralGate(SysctrlPeripheralUart0,TRUE);//开启Uart0外设时钟
stcUartCfg.enRunMode = UartMskMode1;//模式1
stcUartCfg.enStopBit = UartMsk1bit;//1位停止位
switch(BaudRate)
{
case Value1200: stcUartCfg.stcBaud.u32Baud = 1200; break;
case Value2400: stcUartCfg.stcBaud.u32Baud = 2400; break;
case Value4800: stcUartCfg.stcBaud.u32Baud = 4800; break;
case Value9600: stcUartCfg.stcBaud.u32Baud = 9600; break;
case Value19200: stcUartCfg.stcBaud.u32Baud = 19200; break;
case Value38400: stcUartCfg.stcBaud.u32Baud = 38400; break;
case Value57600: stcUartCfg.stcBaud.u32Baud = 57600; break;
case Value115200: stcUartCfg.stcBaud.u32Baud = 115200; break;
}
stcUartCfg.stcBaud.enClkDiv = UartMsk8Or16Div;//8分频
stcUartCfg.stcBaud.u32Pclk = Sysctrl_GetPClkFreq();
Uart_Init(M0P_UART0, &stcUartCfg);
// 再开启中断
Uart_ClrStatus(M0P_UART0, UartRC);
Uart_EnableIrq(M0P_UART0,UartRxIrq);//接收中断
EnableNvic(UART0_2_IRQn, IrqLevel3, TRUE);
}
int fputc(int ch, FILE *f) //重定向printf函数到uart0串口打印
{
(void)f;//防止编译报警告
Uart_SendDataPoll(M0P_UART0, (uint8_t)ch);
return ch;
}
IIC简介
HC32L96PCTA的I2C控制器支持以下特性:
■ 支持主机发送/接收,从机发送/接收四种工作模式
■ 支持标准(100Kbps) / 快速(400Kbps) / 高速(1Mbps) 三种工作速率
■ 支持7位寻址功能
■ 支持噪声过滤功能
■ 支持广播地址
■ 支持中断状态查询功能
由于我们这里只有一个从机设备(其地址为0x53),因此用到的IIC功能其实用软件模拟也能实现,当然这里由于是测评,用的是硬件驱动。
其初始化代码如下:
#define MyIIC_SCL_PORT (GpioPortB)
#define MyIIC_SCL_PIN (GpioPin8)
#define MyIIC_SDA_PORT (GpioPortB)
#define MyIIC_SDA_PIN (GpioPin9)
// IO端口配置
static void MyIIC_PortCfg(void)
{
stc_gpio_cfg_t stcGpioCfg;
DDL_ZERO_STRUCT(stcGpioCfg);
Sysctrl_SetPeripheralGate(SysctrlPeripheralGpio,TRUE); //开启GPIO时钟门控
stcGpioCfg.enDir = GpioDirOut; ///< 端口方向配置->输出
stcGpioCfg.enOD = GpioOdEnable; ///< 开漏输出
stcGpioCfg.enPu = GpioPuEnable; ///< 端口上拉配置->使能
stcGpioCfg.enPd = GpioPdDisable; ///< 端口下拉配置->禁止
stcGpioCfg.bOutputVal = TRUE;
Gpio_Init(MyIIC_SCL_PORT,MyIIC_SCL_PIN,&stcGpioCfg); ///< 端口初始化
Gpio_Init(MyIIC_SDA_PORT,MyIIC_SDA_PIN,&stcGpioCfg);
Gpio_SetAfMode(MyIIC_SCL_PORT,MyIIC_SCL_PIN,GpioAf1); ///< 配置PB08为SCL
Gpio_SetAfMode(MyIIC_SDA_PORT,MyIIC_SDA_PIN,GpioAf1); ///< 配置PB09为SDA
}
// I2C 模块配置
void MyI2c_Config(void)
{
stc_i2c_cfg_t stcI2cCfg;
DDL_ZERO_STRUCT(stcI2cCfg); ///< 初始化结构体变量的值为0
Sysctrl_SetPeripheralGate(SysctrlPeripheralI2c0,TRUE); ///< 开启I2C0时钟门控
stcI2cCfg.u32Pclk = Sysctrl_GetPClkFreq(); ///< 获取PCLK时钟
stcI2cCfg.u32Baud = 100000; ///< 100kHz
stcI2cCfg.enMode = I2cMasterMode; ///< 主机模式
stcI2cCfg.u8SlaveAddr = IIC_ADDR; ///< 从地址,主模式无效
stcI2cCfg.bGc = FALSE; ///< 广播地址应答使能关闭
I2C_Init(M0P_I2C0,&stcI2cCfg); ///< 模块初始化
MyIIC_PortCfg();// GPIO口配置
delay1ms(2);
I2C_SetFunc(M0P_I2C0,I2cStart_En); //开始信号
}
在初始化完成后,就根据例程中提供的数据读取和写入函数,进行简单的修改:/**
******************************************************************************
** \brief 主机接收函数
**
** \param REGAddr从机寄存器地址,pu8Data读数据存放缓存,u32Len读数据长度
**
** \retval 读数据是否成功
**
******************************************************************************/
en_result_t I2C_MasterReadData(M0P_I2C_TypeDef* I2CX,uint8_t *pu8Data,uint32_t u32Len,uint8_t REGAddr)
{
en_result_t enRet = Error;
uint8_t u8i=0,u8State;
I2C_SetFunc(I2CX,I2cStart_En);
while(1)
{
while(0 == I2C_GetIrq(I2CX))
{;}
u8State = I2C_GetState(I2CX);
switch(u8State)
{
case 0x08: //已发送起始条件,将发送SLA+R
I2C_ClearFunc(I2CX,I2cStart_En);
I2C_WriteByte(I2CX,IIC_WRITE_ADDR); //发送SLA+W
break;
case 0x18: //已发送SLA+W,并接收到ACK
I2C_WriteByte(I2CX,REGAddr); //发送寄存器地址
break;
case 0x28: //已发送数据,接收到ACK
I2C_SetFunc(I2CX,I2cStart_En);
break;
case 0x10: //已发送重复起始条件
I2C_ClearFunc(I2CX,I2cStart_En);
I2C_WriteByte(I2CX,IIC_READ_ADDR); //读命令发送
break;
case 0x40: //已发送SLA+R,并接收到ACK
if(u32Len>1)
{
I2C_SetFunc(I2CX,I2cAck_En);
}
break;
case 0x50: //已接收数据字节,并已返回ACK信号
pu8Data[u8i++] = I2C_ReadByte(I2CX);
if(u8i==u32Len-1)
{
I2C_ClearFunc(I2CX,I2cAck_En); //读数据时,倒数第二个字节ACK关闭
}
break;
case 0x58: //已接收到最后一个数据,NACK已返回
pu8Data[u8i++] = I2C_ReadByte(I2CX);
I2C_SetFunc(I2CX,I2cStop_En); //发送停止条件
break;
case 0x38: //在发送地址或数据时,仲裁丢失
I2C_SetFunc(I2CX,I2cStart_En); //当总线空闲时发起起始条件
break;
case 0x48: //发送SLA+R后,收到一个NACK
I2C_SetFunc(I2CX,I2cStop_En);
I2C_SetFunc(I2CX,I2cStart_En);
break;
default: //其他错误状态,重新发送起始条件
I2C_SetFunc(I2CX,I2cStart_En); //其他错误状态,重新发送起始条件
break;
}
I2C_ClearIrq(I2CX); //清除中断状态标志位
if(u8i==u32Len) //数据全部读取完成,跳出while循环
{
break;
}
}
enRet = Ok;
return enRet;
}
/**
******************************************************************************
** \brief 主机发送函数
**
** \param REGAddr从机寄存器地址,pu8Data写数据,u32Len写数据长度,REGAddr操作寄存器地址
**
** \retval 写数据是否成功
**
******************************************************************************/
en_result_t I2C_MasterWriteData(M0P_I2C_TypeDef* I2CX,uint8_t *pu8Data,uint32_t u32Len,uint8_t REGAddr)
{
en_result_t enRet = Error;
uint8_t u8i=0,u8State,u8Flag=FALSE;
I2C_SetFunc(I2CX,I2cStart_En);
while(1)
{
while(0 == I2C_GetIrq(I2CX))
{;}
u8State = I2C_GetState(I2CX);
switch(u8State)
{
case 0x08: ///已发送起始条件
I2C_ClearFunc(I2CX,I2cStart_En);
I2C_WriteByte(I2CX,IIC_WRITE_ADDR); ///从设备地址发送
break;
case 0x18: ///已发送SLA+W,并接收到ACK
I2C_WriteByte(I2CX,REGAddr);
break;
case 0x28: ///上一次发送数据后接收到ACK
if (u8i < u32Len)
{
I2C_WriteByte(I2CX,pu8Data[u8i++]); ///< 继续发送数据
}
else
{
I2C_SetFunc(I2CX,I2cStop_En); ///< 出停止条件
u8Flag = TRUE;
}
break;
case 0x20: ///上一次发送SLA+W后,收到NACK
break;
case 0x38: ///上一次在SLA+读或写时丢失仲裁
I2C_SetFunc(I2CX,I2cStart_En); ///当I2C总线空闲时发送起始条件
break;
case 0x30: ///已发送I2Cx_DATA中的数据,收到NACK,将传输一个STOP条件
I2C_SetFunc(I2CX,I2cStop_En); ///发送停止条件
break;
default:
break;
}
I2C_ClearIrq(I2CX); ///清除中断状态标志位
if(u8Flag == TRUE) ///< 数据发送完成
{
break;
}
}
enRet = Ok;
return enRet;
}
自此,我们初步完成了I2C的初始化。
ADXL345简介
一款加速度计,就不多介绍了,主要检测其芯体在空间中X、Y、Z轴的数据在此,对其进行初始化和数据的采集
//ADXL345设置偏移量
void ADXL345_SetOffset(char xOffset, char yOffset, char zOffset)
{
union
{
unsigned char u8t;// 无符号
char q8t;// 有符号
}X_Offset;
union
{
unsigned char u8t;// 无符号
char q8t;// 有符号
}Y_Offset;
union
{
unsigned char u8t;// 无符号
char q8t;// 有符号
}Z_Offset;
X_Offset.q8t = xOffset;
Y_Offset.q8t = yOffset;
Z_Offset.q8t = zOffset;
I2C_MasterWriteData(M0P_I2C0, &X_Offset.u8t, 1, ADXL345_OFSX);
I2C_MasterWriteData(M0P_I2C0, &Y_Offset.u8t, 1, ADXL345_OFSY);
I2C_MasterWriteData(M0P_I2C0, &Z_Offset.u8t, 1, ADXL345_OFSZ);
}
数据初始化
//ADXL345初始化配置
void ADXL345_Init(void)
{
MyI2c_Config();
delay1ms(10);
if(ADXL345_InitFlag==0)
{
ADXL345_Ready();//ADXL345初始化配置
}
}
//ADXL345准备初始化
void ADXL345_Ready(void)
{
I2C_MasterReadData(M0P_I2C0, ADXL345_Buffer, 1, ADXL345_DEVID);
delay1ms(2);
if(ADXL345_Buffer[0]==0xE5)//ADXL345的器件ID为0xE5
{
ADXL345_Buffer[0] = 0x08;
I2C_MasterWriteData(M0P_I2C0, ADXL345_Buffer, 1, ADXL345_POWER_CTL);
delay1ms(3);
ADXL345_SetOffset((char)4,(char)0,(char)-7);//设置三个轴偏移量
ADXL345_InitFlag = 1;
}
else//加速度传感器坏
{
ADXL345_InitFlag = 0;
}
}
数据采集处理打印输出,需要对其进行数据转换和滤波
//ADXL345的X,Y,Z轴数据读取
void ADXL345_GetXYZValue(float *x, float *y, float *z)
{
float X_Temp=0,Y_Temp=0,Z_Temp=0;
I2C_MasterReadData(M0P_I2C0, ADXL345_Buffer, 6, ADXL345_DATAX0);
X_Temp += (float)((short)(ADXL345_Buffer[1] << 8) + ADXL345_Buffer[0])/256;//数据组合再除以256(即转换为g为单位)
Y_Temp += (float)((short)(ADXL345_Buffer[3] << 8) + ADXL345_Buffer[2])/256;
Z_Temp += (float)((short)(ADXL345_Buffer[5] << 8) + ADXL345_Buffer[4])/256;
*x = X_Temp;
*y = Y_Temp;
*z = Z_Temp;
}
//ADXL345数据收集,处理,输出
void ADXL345_Data_collection(void)
{
float X_out = 0.0;
float Y_out = 0.0;
float Z_out = 0.0;
float roll = 0.0;
float pitch = 0.0;
if(ADXL345_InitFlag==1)//ADXL345初始化成功了
{
delay1ms(10);
ADXL345_GetXYZValue(&X_out, &Y_out, &Z_out);
roll = atan(Y_out / sqrt(pow(X_out, 2) + pow(Z_out, 2))) * 180 / PI;
pitch = atan(-1 * X_out / sqrt(pow(Y_out, 2) + pow(Z_out, 2))) * 180 / PI;
// Low-pass filter
rollF = 0.94 * rollF + 0.06 * roll;
pitchF = 0.94 * pitchF + 0.06 * pitch;
printf("%.2f/%.2f\r\n",rollF, pitchF);
}
else
{
printf("ADXL345 Failure !!!");
}
}
而后,在main函数中对其进行调用时钟配置(和上期一样)
// 系统时钟配置
void SYS_ClkConfig(void)
{
stc_sysctrl_clk_cfg_t stcCfg;
Flash_WaitCycle(FlashWaitCycle0);//flash读等待周期
Sysctrl_SetRCHTrim(SysctrlRchFreq24MHz);// RCH = 16MHZ
Sysctrl_SetRCLTrim(SysctrlRclFreq32768);// RCL = 32.786kHz
///< 选择内部 RCH 作为HCLK时钟源;
stcCfg.enClkSrc = SysctrlClkRCH;// 系统时钟为24Mhz
///< HCLK SYSCLK/2
stcCfg.enHClkDiv = SysctrlHclkDiv2; //HCLK = 12MHz
///< PCLK 为HCLK/1
stcCfg.enPClkDiv = SysctrlPclkDiv1; //PCLK = 12MHz
///< 系统时钟初始化
Sysctrl_ClkInit(&stcCfg);
//使能RCL
Sysctrl_ClkSourceEnable(SysctrlClkRCL, TRUE);
}
总体硬件配置
// 硬件处理
void APP_System_Init(void)
{
SYS_ClkConfig(); // 时钟初始化配置
// Rtc_InitConfig(); // RTC时钟配置
Uart0_Init(); // Uart初始化配置
// Lcd_ConfigRead(); // LCD显示配置
ADXL345_Init(); // ADXL345初始化
}
main函数int32_t main(void)
{
// 硬件模块初始化
APP_System_Init();
while(1)
{
delay1ms(10);
//Rtc_Time_Display(); // RTC时间显示
ADXL345_Data_collection();// ADXL345数据发送
}
}
最终,我们可以看到如下视频效果:
其展示姿态的软件为 processing
最后,将代码放到附件,供下载学习交流
|