- 2025-02-14
-
回复了主题帖:
【MCXA156开发板测评】LPI2C pk 模拟I2C,孰强孰弱?
本帖最后由 不语arc 于 2025-2-14 14:35 编辑
freebsder 2025-2-14 14:23
嗯嗯,对比参考一下。模拟好移植,有些开发板没有硬件i2c,或者设计存在坑,这种情况下使用模拟i2c还是很方便的
-
回复了主题帖:
【MCXA156开发板测评】LPI2C pk 模拟I2C,孰强孰弱?
lugl4313820 发表于 2025-2-14 13:44
有个计算fps的程序,然后在线展示,那就牛了。
哈哈哈哈哈,建议不错
-
回复了主题帖:
【MCXA156开发板测评】LPI2C外设驱动OLED
本帖最后由 不语arc 于 2025-2-14 08:58 编辑
humancat01 2025-2-14 07:06 оùоо
我也支持国产芯片,这和学习国外优秀的芯片不冲突。参考对比能让技术视野更开阔,也更能督促国产芯片的进步
-
回复了主题帖:
【MCXA156开发板测评】LPTMR初体验
Jacktang 2025-2-14 07:48 LPTMR
是的,配置比较常规
- 2025-02-13
-
发表了主题帖:
【MCXA156开发板测评】LPI2C pk 模拟I2C,孰强孰弱?
本帖最后由 不语arc 于 2025-2-14 14:31 编辑
1. 背景
在前面已经分别实现了模拟I2C和LPI2C对oled的驱动,同样使用i2c协议,哪种性能速度更快呢?
在本篇将以oled的刷写速度进行对比。
2. 实验安排
配置好1ms中断,分别安排硬件I2C和模拟I2C执行相同的任务:对oled进行十次刷屏,通过比较总耗时来判断速度。
代码如下:
/*
* Copyright (c) 2015, Freescale Semiconductor, Inc.
* Copyright 2016-2017 NXP
* All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "fsl_debug_console.h"
#include "board.h"
#include "app.h"
#include "peripherals.h"
#include "oled.h"
#include "fsl_lptmr.h"
/*******************************************************************************
* Definitions
******************************************************************************/
/*******************************************************************************
* Prototypes
******************************************************************************/
/*******************************************************************************
* Variables
******************************************************************************/
volatile uint32_t lptmrCounter = 0U;
/*******************************************************************************
* Code
******************************************************************************/
void LPTMR_LED_HANDLER(void)
{
LPTMR_ClearStatusFlags(DEMO_LPTMR_BASE, kLPTMR_TimerCompareFlag);
lptmrCounter++;
/*
* Workaround for TWR-KV58: because write buffer is enabled, adding
* memory barrier instructions to make sure clearing interrupt flag completed
* before go out ISR
*/
__DSB();
__ISB();
}
/*!
* @brief Main function
*/
int main(void)
{
uint32_t start_cnt, end_cnt;
uint32_t total_time_ms = 0;
u8 test_rounds = 20; // 测试轮数
/* Board pin, clock, debug console init */
BOARD_InitHardware();
LED_INIT();
BOARD_InitBootPeripherals();
OLED_Init();
OLED_Clear();
PRINTF("Low Power Timer Example\r\n");
u8 color = 1;
for (u8 i = 0; i < test_rounds; i++) {
start_cnt = lptmrCounter;
OLED_FillScreen(color);
color = !color;
end_cnt = lptmrCounter;
total_time_ms += (end_cnt - start_cnt);
}
PRINTF("total_time_ms = %d\r\n", total_time_ms);
while (1)
{
}
}
3. 实验结果
表格解释:
第一列分别对应了硬件I2C和模拟I2C两组实验。
第二列对应了编译优化选项。LPI2C默认使用o1优化,而在模拟i2c中使用o1优化会造成时序错乱。这是一个坑。(GPIO 模拟 I2C 时序需要严格的时间控制。编译器优化可能会改变代码的执行顺序或删除某些看似“无用”的代码(如空循环或延时函数),从而导致时序不准确。)
第三列是i2c频率,在硬件i2c中通过更改配置,可以很方便改变频率。模拟i2c并未找到配置gpio速度的参数(可能存在,这里使用的是默认值)
第四列是执行相同刷屏任务的耗时。单位为ms
结果分析:
在硬件i2c中,从100khz到1.5Mhz,频率的改变可以很明显地发现,耗时几乎成反比。同时多次实验总耗时的具体数值完全不变,这证明了LPI2C模块的精确性!
而硬件i2c的频率设置也存在上限,拉高到1.6Mhz时,与1.5Mhz几户没有变化。而再拉高频率开发板会进入错误状态,无法执行任务。
在同样的编译优化o0情况下,模拟i2c的速度稍微比硬件i2c的400k速度慢一点点。
总之,LPI2C模块非常精准,速度超快,满足绝大多数i2c场景。
4.实验过程展示
o1优化,硬件i2c场景:
[localvideo]d35b95cba7425c911d8c41f990cd1bcb[/localvideo]
o0优化,模拟i2c场景:
[localvideo]8c0c87b88ee326443b422ba236046750[/localvideo]
-
发表了主题帖:
【MCXA156开发板测评】LPTMR初体验
本帖最后由 不语arc 于 2025-2-14 08:52 编辑
参考:【MCXA156开发板测评】-3-LPTMR体验 - NXP MCU - 电子工程世界-论坛
1. LPTMR模块
为了比较oled的刷屏速度,需要用到定时器的计数功能,发现nxp的定时器与PWM两种模块,由于只用到计数功能,因此先体验TPTMR模块,参考上述链接的大佬配置了一下。
LPTMR模块能够以极低的功耗运行,即使在系统处于低功耗模式时也能保持工作状态,这使得它非常适合用于需要定时唤醒或周期性事件触发的应用场景。其配置为一个16位的时间计数器或脉冲计数器。
2. config配置
打开sdk实例程序lptmr,以此作为基础。
时钟配置:
LPTMR模块配置:周期1ms(注意第一个框中的时钟源)、开启中断
更新 自动生成代码:
const lptmr_config_t LPTMR0_config = {
.timerMode = kLPTMR_TimerModeTimeCounter,
.pinSelect = kLPTMR_PinSelectInput_0,
.pinPolarity = kLPTMR_PinPolarityActiveHigh,
.enableFreeRunning = false,
.bypassPrescaler = true,
.prescalerClockSource = kLPTMR_PrescalerClock_3,
.value = kLPTMR_Prescale_Glitch_0
};
static void LPTMR0_init(void) {
/* Initialize the LPTMR */
LPTMR_Init(LPTMR0_PERIPHERAL, &LPTMR0_config);
/* Set LPTMR period */
LPTMR_SetTimerPeriod(LPTMR0_PERIPHERAL, LPTMR0_TICKS);
/* Configure timer interrupt */
LPTMR_EnableInterrupts(LPTMR0_PERIPHERAL, kLPTMR_TimerInterruptEnable);
/* Interrupt vector LPTMR0_IRQn priority settings in the NVIC. */
NVIC_SetPriority(LPTMR0_IRQN, LPTMR0_IRQ_PRIORITY);
/* Enable interrupt LPTMR0_IRQN request in the NVIC */
EnableIRQ(LPTMR0_IRQN);
/* Start the LPTMR timer */
LPTMR_StartTimer(LPTMR0_PERIPHERAL);
}
/***********************************************************************************************************************
* Initialization functions
**********************************************************************************************************************/
void BOARD_InitPeripherals(void)
{
/* Initialize components */
LPTMR0_init();
}
/***********************************************************************************************************************
* BOARD_InitBootPeripherals function
**********************************************************************************************************************/
void BOARD_InitBootPeripherals(void)
{
BOARD_InitPeripherals();
}
更新源代码后,配置更改在peripherals.c文件中并且不在工程中,因此需要先将该文件添加到工程的board目录下,再在主程序文件lptmr.c的初始化部分调用BOARD_InitBootPeripherals();函数。
3.应用程序
每隔一秒prtinf打印一次,并翻转led
/*
* Copyright (c) 2015, Freescale Semiconductor, Inc.
* Copyright 2016-2017 NXP
* All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "fsl_debug_console.h"
#include "board.h"
#include "app.h"
#include "peripherals.h"
#include "fsl_lptmr.h"
/*******************************************************************************
* Definitions
******************************************************************************/
/*******************************************************************************
* Prototypes
******************************************************************************/
/*******************************************************************************
* Variables
******************************************************************************/
//volatile uint32_t lptmrCounter = 0U;
/*******************************************************************************
* Code
******************************************************************************/
void LPTMR_LED_HANDLER(void)
{
static uint32_t lptmrCounter = 0;
LPTMR_ClearStatusFlags(DEMO_LPTMR_BASE, kLPTMR_TimerCompareFlag);
lptmrCounter++;
if(lptmrCounter%1000 == 0){
PRINTF("lptmrCounter = %d\r\n", lptmrCounter);
LED_TOGGLE();
}
/*
* Workaround for TWR-KV58: because write buffer is enabled, adding
* memory barrier instructions to make sure clearing interrupt flag completed
* before go out ISR
*/
__DSB();
__ISB();
}
/*!
* [url=home.php?mod=space&uid=159083]@brief[/url] Main function
*/
int main(void)
{
uint32_t currentCounter = 0U;
/* Board pin, clock, debug console init */
BOARD_InitHardware();
LED_INIT();
BOARD_InitBootPeripherals();
PRINTF("Low Power Timer Example\r\n");
while (1)
{
}
}
4. 效果展示
[localvideo]6146161d7c239d1473af692acfc14e40[/localvideo]
-
回复了主题帖:
【MCXA156开发板测评】-3-LPTMR体验
谢谢分享,参考配置了
-
回复了主题帖:
【MCXN947开发板测评】Coremark内核测试
微处理器居然也能跑分,学到了
-
发表了主题帖:
【MCXA156开发板测评】LPI2C外设驱动OLED
本帖最后由 不语arc 于 2025-2-13 15:02 编辑
1. 硬件I2C
在上一篇分享中,使用GPIO模拟I2C实现了对OLED的驱动,在本篇中,我将利用A156强大的硬件I2C外设完成对OLED的驱动。
MCXA156芯片有四个低功耗I2C模块,分别是LPI2C0,LPI2C1,LPI2C2,LPI2C3。每个LPI2C模块通过一对控制信号和数据信号支持串行I2C通信,并且可以作为控制器或目标设备运行。
在本实验中,选择使用LPI2C0模块。
2. config配置
勾选LPI2C0模块
3. 代码
3.1 核心函数:LPI2C发送一字节数据
/*
I2C传出一字节数据函数:
Mode : 数据/命令标识符。 0表示命令,1表示数据(OLED的数据地址一般为0x40;指令地址为0x00)
*Data : 要写入的数据
*/
void OLED_WR_Byte(uint8_t Data, uint8_t Mode) {
lpi2c_master_transfer_t xfer = {0};
/*Start Transfer*/
xfer.data = (uint8_t *)&Data;
xfer.dataSize = sizeof(Data);
xfer.flags = kLPI2C_TransferDefaultFlag;
xfer.slaveAddress = SSD1306_ADDRESS;
xfer.direction = kLPI2C_Write;
if (Mode) { xfer.subaddress = 0x40; }
else { xfer.subaddress = 0x00; }
xfer.subaddressSize = 1;
LPI2C_MasterTransferBlocking(LPI2C0_PERIPHERAL, &xfer);
}
3.2 主函数
/*
* Copyright 2017 NXP
* All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
/* Standard C Included Files */
#include <stdio.h>
#include <string.h>
#include "board.h"
#include "fsl_debug_console.h"
#include "fsl_lpi2c.h"
#include "app.h"
#include "oled.h"
/*******************************************************************************
* Definitions
******************************************************************************/
#define EXAMPLE_I2C_MASTER ((LPI2C_Type *)EXAMPLE_I2C_MASTER_BASE)
#define LPI2C_BAUDRATE 100000U
/*******************************************************************************
* Prototypes
******************************************************************************/
/*******************************************************************************
* Variables
******************************************************************************/
/*******************************************************************************
* Code
******************************************************************************/
/*!
* [url=home.php?mod=space&uid=159083]@brief[/url] Main function
*/
int main(void)
{
lpi2c_master_config_t masterConfig;
BOARD_InitHardware();
LPI2C_MasterGetDefaultConfig(&masterConfig);
/* Change the default baudrate configuration */
masterConfig.baudRate_Hz = LPI2C_BAUDRATE;
/* Initialize the LPI2C master peripheral */
LPI2C_MasterInit(EXAMPLE_I2C_MASTER, &masterConfig, LPI2C_MASTER_CLOCK_FREQUENCY);
OLED_Init();
OLED_Clear();
OLED_8x16(0, 0, "hello, eeWorld");
while (1)
{
}
}
3.3 oled.c
#include "oled.h"
#include "DEV_OLED_8x16.h"
/*
OLED显存的映射数组,存放格式如下:
//[0]0 1 2 3 ... 127
//[1]0 1 2 3 ... 127
//[2]0 1 2 3 ... 127
//[3]0 1 2 3 ... 127
//[4]0 1 2 3 ... 127
//[5]0 1 2 3 ... 127
//[6]0 1 2 3 ... 127
//[7]0 1 2 3 ... 127
*/
u8 OLED_GRAM[128][8];
/*
I2C传出一字节数据函数:
Mode : 数据/命令标识符。 0表示命令,1表示数据(OLED的数据地址一般为0x40;指令地址为0x00)
*Data : 要写入的数据
*/
void OLED_WR_Byte(uint8_t Data, uint8_t Mode) {
lpi2c_master_transfer_t xfer = {0};
/*Start Transfer*/
xfer.data = (uint8_t *)&Data;
xfer.dataSize = sizeof(Data);
xfer.flags = kLPI2C_TransferDefaultFlag;
xfer.slaveAddress = SSD1306_ADDRESS;
xfer.direction = kLPI2C_Write;
if (Mode) { xfer.subaddress = 0x40; }
else { xfer.subaddress = 0x00; }
xfer.subaddressSize = 1;
LPI2C_MasterTransferBlocking(LPI2C0_PERIPHERAL, &xfer);
}
//开启OLED显示
void OLED_DisPlay_On(void)
{
OLED_WR_Byte(0x8D, OLED_CMD);//电荷泵使能
OLED_WR_Byte(0x14, OLED_CMD);//开启电荷泵
OLED_WR_Byte(0xAF, OLED_CMD);//点亮屏幕
}
//关闭OLED显示
void OLED_DisPlay_Off(void)
{
OLED_WR_Byte(0x8D, OLED_CMD);//电荷泵使能
OLED_WR_Byte(0x10, OLED_CMD);//关闭电荷泵
OLED_WR_Byte(0xAE, OLED_CMD);//关闭屏幕
}
//缓冲区写法。更新显存到OLED的GRAM中,此函数仅在OLED_Clear()函数中使用了。
void OLED_Refresh(void)
{
uint8_t i, n;
// 遍历所有页 (0-7)
for(i = 0; i < 8; i++)
{
// 设置页地址
OLED_WR_Byte(0xB0 + i, OLED_CMD); // 每次递增页地址
// 设置列地址低字节 (固定为0x00)
OLED_WR_Byte(0x00, OLED_CMD);
// 设置列地址高字节 (固定为0x10)
OLED_WR_Byte(0x10, OLED_CMD);
// 发送当前页的数据
for(n = 0; n < 128; n++)
{
OLED_WR_Byte(OLED_GRAM[n], OLED_DATA);
}
}
}
void OLED_Clear(void)
{
memset(OLED_GRAM, 0, sizeof(OLED_GRAM));
OLED_Refresh();
}
//OLED页寻址方式,x表示长(0-127),y表示页(0-7)
void OLED_Setxy(u8 x, u8 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);
}
//显示字符串函数
//x:0-127 y:0-7
void OLED_8x16(u8 x, u8 y, u8 ch[])
{
u8 c = 0, i = 0, j = 0;
while (ch[j] != '\0')
{
c = ch[j] - 32;//32为space的asiic码,c为偏移量
if (x > 120) //超出边界判断
{
x = 0;
y++;
}
OLED_Setxy(x, y); //字符取模方式:先横后纵
for (i = 0; i < 8; i++) //一个字的宽为8个像素
OLED_WR_Byte(F8X16[c * 16 + i], OLED_DATA);
OLED_Setxy(x, y + 1);
for (i = 0; i < 8; i++)
OLED_WR_Byte(F8X16[c * 16 + i + 8], OLED_DATA);
x += 8;
j++;
}
}
//OLED的初始化
void OLED_Init(void)
{
OLED_WR_Byte(0xAE, OLED_CMD);//--turn off oled panel
OLED_WR_Byte(0x00, 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_Clear();
OLED_WR_Byte(0xAF, OLED_CMD);
}
4. 效果演示
- 2025-02-12
-
回复了主题帖:
【MCXA156开发板测评】模拟I2C驱动 OLED屏
本帖最后由 不语arc 于 2025-2-12 14:39 编辑
jobszheng5 2025-2-12 10:15 I2CЭ鶼IO
GPIO好移植,硬件I2C再试试进行对比
- 2025-02-11
-
发表了主题帖:
【MCXA156开发板测评】模拟I2C驱动 OLED屏
本帖最后由 不语arc 于 2025-2-11 20:50 编辑
1. 模拟I2C
模拟I2C也就是使用GPIO管脚来模拟实现I2C时序,完成对I2C设备的驱动。相较于核心板自带的硬件I2C,模拟I2C有利有弊,
优点在于灵活性高,可以在任何支持GPIO的微控制器上实现,移植方便,不依赖硬件I2C外设,可以自己定义时序,适应不同的设备需求,引脚选择也不受限制。
缺点在于占用CPU资源,由于是软件模拟,一方面速度较慢,另一方面时序精度不足,需要手动实现I2C协议的起始、停止、ACK、数据读写增加了代码量工作。
2. I2C协议
I2C使用两条双向开漏线或开集线进行通信:串行数据线(SDA)和串行时钟线(SCL)。使用主从架构:通信由一个或多个主机控制,这些主机启动与从设备的数据传输。每个从设备都有一个唯一的地址,主机通过这个地址来指定要与之通信的从设备。有三种速率:标准模式下速率为100 kbit/s,快速模式下可达400 kbit/s,高速模式下可达3.4 Mbit/s。
时序图:
3. GPIO初始化
分别使用P1_0、P1_2作为SCL和SDA
gpio_pin_config_t gpio1_pin91_config = {
.pinDirection = kGPIO_DigitalOutput,
.outputLogic = 0U
};
/* Initialize GPIO functionality on pin PIO1_0 (pin 91) */
GPIO_PinInit(GPIO1, 0U, &gpio1_pin91_config);
gpio_pin_config_t gpio1_pin93_config = {
.pinDirection = kGPIO_DigitalOutput,
.outputLogic = 0U
};
/* Initialize GPIO functionality on pin PIO1_2 (pin 93) */
GPIO_PinInit(GPIO1, 2U, &gpio1_pin93_config);
使用GPIO_PinInit 函数初始化了以下 GPIO 引脚:
GPIO1_0 (pin 91):配置为输出,初始状态为低电平。
GPIO1_2 (pin 93):配置为输出,初始状态为低电平。
/* PORT1_0 (pin 91) is configured as P1_0, LPTMR0_ALT3, WUU0_IN6 */
PORT_SetPinMux(PORT1, 0U, kPORT_MuxAlt0);
PORT1->PCR[0] =
((PORT1->PCR[0] &
/* Mask bits to zero which are setting */
(~(PORT_PCR_PS_MASK | PORT_PCR_PE_MASK | PORT_PCR_ODE_MASK | PORT_PCR_DSE_MASK | PORT_PCR_IBE_MASK)))
/* Pull Select: Enables internal pullup resistor. */
| PORT_PCR_PS(PCR_PS_ps1)
/* Pull Enable: Enables. */
| PORT_PCR_PE(PCR_PE_pe1)
/* Open Drain Enable: Enables. */
| PORT_PCR_ODE(PCR_ODE_ode1)
/* Drive Strength Enable: High. */
| PORT_PCR_DSE(PCR_DSE_dse1)
/* Input Buffer Enable: Enables. */
| PORT_PCR_IBE(PCR_IBE_ibe1));
/* PORT1_2 (pin 93) is configured as P1_2 */
PORT_SetPinMux(PORT1, 2U, kPORT_MuxAlt0);
PORT1->PCR[2] =
((PORT1->PCR[2] &
/* Mask bits to zero which are setting */
(~(PORT_PCR_PS_MASK | PORT_PCR_PE_MASK | PORT_PCR_ODE_MASK | PORT_PCR_DSE_MASK | PORT_PCR_IBE_MASK)))
/* Pull Select: Enables internal pullup resistor. */
| PORT_PCR_PS(PCR_PS_ps1)
/* Pull Enable: Enables. */
| PORT_PCR_PE(PCR_PE_pe1)
/* Open Drain Enable: Enables. */
| PORT_PCR_ODE(PCR_ODE_ode1)
/* Drive Strength Enable: High. */
| PORT_PCR_DSE(PCR_DSE_dse1)
/* Input Buffer Enable: Enables. */
| PORT_PCR_IBE(PCR_IBE_ibe1));
通过直接操作寄存器 PORT1->PCR[x] 配置了以下特性:
上拉/下拉电阻:启用上拉电阻。
开漏输出:启用开漏输出模式。
驱动强度:配置引脚为高驱动驱动能力。
4. I2C协议实现
4.1 宏定义
/* 定义I2C总线连接的GPIO端口 */
#define OLED_I2C_GPIO_PORT GPIO0 /* GPIO端口 */
#define OLED_I2C_SCL_PIN 0U /* 连接到SCL时钟线的GPIO */
#define OLED_I2C_SDA_PIN 2U /* 连接到SDA数据线的GPIO */
//-----------------OLED引脚宏定义----------------
#define OLED_SCL_Clr() GPIO_PortClear(OLED_I2C_GPIO_PORT,OLED_I2C_SCL_PIN)//SCL
#define OLED_SCL_Set() GPIO_PortSet(OLED_I2C_GPIO_PORT,OLED_I2C_SCL_PIN)
#define OLED_SDA_Clr() GPIO_PortClear(OLED_I2C_GPIO_PORT,OLED_I2C_SDA_PIN)//SDA
#define OLED_SDA_Set() GPIO_PortSet(OLED_I2C_GPIO_PORT,OLED_I2C_SDA_PIN)
#define OLED_CMD 0 //指令
#define OLED_DATA 1 //数据
4.2 起始信号
//起始信号
void I2C_Start(void)
{
OLED_SDA_Set();
OLED_SCL_Set();
IIC_delay();
OLED_SDA_Clr();
IIC_delay();
OLED_SCL_Clr();
IIC_delay();
}
4.3 结束信号
//结束信号
void I2C_Stop(void)
{
OLED_SDA_Clr();
OLED_SCL_Set();
IIC_delay();
OLED_SDA_Set();
}
4.4 等待ack信号
void I2C_WaitAck(void)
{
OLED_SDA_Set();
IIC_delay();
OLED_SCL_Set();
IIC_delay();
OLED_SCL_Clr();
IIC_delay();
}
4.5 通过I2C发送一字节函数
//发送一个字节
void Send_Byte(u8 dat)
{
u8 i;
for (i = 0;i < 8;i++)
{
if (dat & 0x80)//将data的8位依次写入
{
OLED_SDA_Set();
}
else
{
OLED_SDA_Clr();
}
IIC_delay();
OLED_SCL_Set();
IIC_delay();
OLED_SCL_Clr();//将时钟信号设置为低电平
dat <<= 1;
}
}
4.6 写一字节数据/命令到OLED中
流程:先发送从机地址(0.96寸oled的主控芯片SSD1306的从机地址为0x78),再发送数据/命令对应的指令,最后发送具体data。
//将一个字节写入OLED中
//mode:数据/命令标识符;0表示命令,1表示数据
void OLED_WR_Byte(u8 dat, u8 mode)
{
I2C_Start();
Send_Byte(0x78);
I2C_WaitAck();
if (mode) { Send_Byte(0x40); }
else { Send_Byte(0x00); }
I2C_WaitAck();
Send_Byte(dat);
I2C_WaitAck();
I2C_Stop();
}
5. 应用函数,在oled上显示hello eeWorld!
//OLED页寻址方式,x表示长(0-127),y表示页(0-7)
void OLED_Setxy(u8 x, u8 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);
}
//显示字符串函数
//x:0-127 y:0-7
void OLED_8x16(u8 x, u8 y, u8 ch[])
{
u8 c = 0, i = 0, j = 0;
while (ch[j] != '\0')
{
c = ch[j] - 32;//32为space的asiic码,c为偏移量
if (x > 120) //超出边界判断
{
x = 0;
y++;
}
OLED_Setxy(x, y); //字符取模方式:先横后纵
for (i = 0; i < 8; i++) //一个字的宽为8个像素
OLED_WR_Byte(F8X16[c * 16 + i], OLED_DATA);
OLED_Setxy(x, y + 1);
for (i = 0; i < 8; i++)
OLED_WR_Byte(F8X16[c * 16 + i + 8], OLED_DATA);
x += 8;
j++;
}
}
在主函数中,while(1)之前,调用函数
OLED_8x16(0, 0, "hello, eeWorld!");
6. 效果演示
-
回复了主题帖:
【MCXA156开发板测评】串口通讯及vofa+上位机配置
秦天qintian0303 发表于 2025-2-11 09:22
MCXA156微控制器拥有五个低功耗通用异步收发器(LPUART)模块:LPUART0、LPUART1、LPUART2、LPUART3和LPUAR ...
是的,引出了不过 LPURAT3和LPUART4被复用成了FlexIO / LCD模块针脚
- 2025-02-10
-
发表了主题帖:
【MCXA156开发板测评】串口通讯及vofa+上位机配置
本帖最后由 不语arc 于 2025-2-10 20:01 编辑
1.LPUART资源
MCXA156微控制器拥有五个低功耗通用异步收发器(LPUART)模块:LPUART0、LPUART1、LPUART2、LPUART3和LPUART4。然而,FRDM-MCXA156开发板仅支持与LPUART0、LPUART1和LPUART2模块之间的通信。下图展示了FRDM-MCXA156的LPUART结构图。
LPUART0模块被称为MCU-Link,可以作为USB到UART桥使用,通过虚拟通信(VCOM)端口调试MCXA156。
LPUART1是一个拥有8个位置的mikroBUS插座连接器,允许插入的mikroBUS click板通过UART连接与MCXA156 MCU进行通信。
LPUART2是一个双排各8个位置的Arduino插座连接器,允许插入的Arduino板通过UART连接与MCXA156 MCU进行通信。
因此选择开发板预留的LPUART0作为串口接口。
2. 代码配置
导入SDK中的frdmmcxa156_lpuart_polling 工程作为基准。
2.1 配置的初始化结构体 lpuart_config_t:
/*! @brief LPUART configuration structure. */
typedef struct _lpuart_config
{
uint32_t baudRate_Bps; /*!< LPUART baud rate */
lpuart_parity_mode_t parityMode; /*!< Parity mode, disabled (default), even, odd */
lpuart_data_bits_t dataBitsCount; /*!< Data bits count, eight (default), seven */
bool isMsb; /*!< Data bits order, LSB (default), MSB */
#if defined(FSL_FEATURE_LPUART_HAS_STOP_BIT_CONFIG_SUPPORT) && FSL_FEATURE_LPUART_HAS_STOP_BIT_CONFIG_SUPPORT
lpuart_stop_bit_count_t stopBitCount; /*!< Number of stop bits, 1 stop bit (default) or 2 stop bits */
#endif
#if defined(FSL_FEATURE_LPUART_HAS_FIFO) && FSL_FEATURE_LPUART_HAS_FIFO
uint8_t txFifoWatermark; /*!< TX FIFO watermark */
uint8_t rxFifoWatermark; /*!< RX FIFO watermark */
#endif
#if defined(FSL_FEATURE_LPUART_HAS_MODEM_SUPPORT) && FSL_FEATURE_LPUART_HAS_MODEM_SUPPORT
bool enableRxRTS; /*!< RX RTS enable */
bool enableTxCTS; /*!< TX CTS enable */
lpuart_transmit_cts_source_t txCtsSource; /*!< TX CTS source */
lpuart_transmit_cts_config_t txCtsConfig; /*!< TX CTS configure */
#endif
lpuart_idle_type_select_t rxIdleType; /*!< RX IDLE type. */
lpuart_idle_config_t rxIdleConfig; /*!< RX IDLE configuration. */
bool enableTx; /*!< Enable TX */
bool enableRx; /*!< Enable RX */
} lpuart_config_t;
关键参数:
baudRate_Bps:指定LPUART的波特率,即每秒传输的比特数。
parityMode:定义了奇偶校验模式,可以是无校验(默认)、偶校验或奇校验。
dataBitsCount:指定了数据位的数量,默认为8位,也可以设置为7位。数据位是实际传输的数据部分。
isMsb:确定数据位的顺序,最低有效位(LSB)优先(默认),或者最高有效位(MSB)优先。这决定了数据在传输线上的排列顺序。
enableTx / enableRx:布尔值,用于分别启用或禁用发送和接收功能。
2.2 核心函数:开发板发送数据
传入参数: LPUART 外设基地址的指针、写入的数据起始地址、数据的大小(以字节为单位)
status_t LPUART_WriteBlocking(LPUART_Type *base, const uint8_t *data, size_t length)
{
assert(NULL != data);
const uint8_t *dataAddress = data;
size_t transferSize = length;
#if UART_RETRY_TIMES
uint32_t waitTimes;
#endif
while (0U != transferSize)
{
#if UART_RETRY_TIMES
waitTimes = UART_RETRY_TIMES;
while ((0U == (base->STAT & LPUART_STAT_TDRE_MASK)) && (0U != --waitTimes))
#else
while (0U == (base->STAT & LPUART_STAT_TDRE_MASK))
#endif
{
}
#if UART_RETRY_TIMES
if (0U == waitTimes)
{
return kStatus_LPUART_Timeout;
}
#endif
base->DATA = *(dataAddress);
dataAddress++;
transferSize--;
}
/* Ensure all the data in the transmit buffer are sent out to bus. */
#if UART_RETRY_TIMES
waitTimes = UART_RETRY_TIMES;
while ((0U == (base->STAT & LPUART_STAT_TC_MASK)) && (0U != --waitTimes))
#else
while (0U == (base->STAT & LPUART_STAT_TC_MASK))
#endif
{
}
#if UART_RETRY_TIMES
if (0U == waitTimes)
{
return kStatus_LPUART_Timeout;
}
#endif
return kStatus_Success;
}
LPUART_WriteBlocking 函数是一个用于通过 LPUART 模块发送数据的阻塞式写入函数。它通过轮询(polling)的方式等待 LPUART 发送寄存器为空或 TX FIFO 有空间,然后将数据写入到发送缓冲区,并继续等待直到所有数据都被发送出去。
3.上位机vofa+
vofa+上位机功能丰富、界面高级,而且免费 ,非常推荐。
vofa+有多种发送格式,具体如下:
RawData 协议,适用于那些不需要解析数据、仅仅查看字节流的需求。在这种模式下,VOFA+ 充当一个普通的串口助手,接收到的数据会直接显示出来。
FireWater 协议,是一种非常直观简洁的与 VOFA+ 上位机通信的协议。它的设计灵感来源于 CSV(逗号分隔值)文件格式,通过将print函数重定向即可直接使用,注意数据之间用逗号隔开,并且每帧数据后加上换行符。这种协议非常适合用于打印出简单的数值型数据,并在 VOFA+ 中绘制动态曲线图。
JustFloat 协议,是专门针对需要高效传输大量浮点数数据的情况设计的。该协议采用小端浮点数组的形式进行字节流传输,这样可以节省带宽并且保持较高的准确性。每个数据包后面通常附有一个特定的帧尾标志来标识数据包的结束。
我使用JustFloat发送格式,只需要在包尾发送对应的tail[4]{0x00,0x00,0x80,0x7f}即可上传多个通道的数据。
发送函数:void vofa_JustFloat_output(float s1,float s2,float s3,float s4)
{
float data[4];
uint8_t tail[4] = {0x00,0x00,0x80,0x7f};
//发送数据
data[0] = s1;
data[1] = s2;
data[2] = s3;
data[3] = s4;
LPUART_WriteBlocking(DEMO_LPUART,(uint8_t*)data,sizeof(float)* 4); //发送数据
//发送帧尾
LPUART_WriteBlocking(DEMO_LPUART,tail,4);
}
4.应用函数测试
编写4个正弦方程测试函数,每个变量的步长随意。
void vofa_FireWater_output_test (void)
{
static float t1 = 0;
static float t2 = 0;
static float t3 = 0;
static float t4 = 0;
vofa_JustFloat_output(sin(t1), cos(t2),sin(2*t3), cos(2*t4));
t1 = t1+0.1;
t2 = t2+0.2;
t3 = t3+0.5;
t4 = t4+0.3;
}
在while(1)中测试
while (1)
{
vofa_FireWater_output_test();
for(ch=0xffff;ch>0;ch--); //延时
}
在vofa+软件中,拖拽xy轴到显示界面,右键选择y轴,再点击all,即可看到四个波形。
选择0.1的步长,可以发现生成的四条正弦波很标准!
总结,LPUART模块非常方便,验证成功
-
回复了主题帖:
【极海APM32M3514电机通用评估板】ADC电流采样 公式推理与验证
lugl4313820 2025-2-4 21:43
10
лл
- 2025-01-07
-
回复了主题帖:
【极海APM32M3514电机通用评估板】外设篇(求助ADC)
已解决。
为什么会发生循环执行无法退出ADC中断的情况,是因为ADC中断处理函数 的执行时间 大于 ADC触发周期。因此产生了退不出中断的错觉。
- 2025-01-06
-
回复了主题帖:
【极海APM32M3514电机通用评估板】ADC电流采样 公式推理与验证
破案了,采样的UI是被SUM抬高了的,所以,在运放的反向输入端接SUM也就对应了 减去SUM偏置。所以 求ADC实际采样值,就等效了情况二,直接使用情况二的公式即可。
- 2024-12-26
-
发表了主题帖:
【极海APM32M3514电机通用评估板】ADC电流采样 公式推理与验证
本帖最后由 不语arc 于 2024-12-26 11:39 编辑
1.背景
在上一篇分享中,我提到了一个问题:
为什么U、V两相的运放反相输入端为什么是实际的SUM?官方给出的计算公式,是按照反相输入端接地计算得到的,虽然SUM值不算大。
在这里我对两种情况分别讨论,用实验结果来验证。
2.分组实验
2.1 情况一:U、V两相的运放反相输入端是总电流SUM
重新推理公式,虚短和虚断:
计算得:
因此:其中,Uo为运放输出值,也就是ADC实际采样的值,Uin为采样电阻两端的电压值。
由公式修改ADC采样函数
typedef struct
{
float Ia; // Phase A current
float Ib; // Phase B current
float Ic; // Phase C current
float Ibus;
float Vbus;
float VHandle; //0-3.3的范围
uint16_t ADC_Ia; // Phase A Voltage(I*R)
uint16_t ADC_Ib; // Phase B Voltage
uint16_t ADC_Ibus; // SUM Voltage
uint16_t ADC_Ia_offset; // 电流零点偏置
uint16_t ADC_Ib_offset; // 电流零点偏置
uint16_t ADC_Ibus_offset; // 电流零点偏置
uint16_t ADC_Vbus; // Phase bus Voltage
uint16_t ADC_Handle;
} CURRENT_Def;
#define ADC_V_K 0.0008 // 3.3/4096.0 // ADC 与电压转换系数
#define VREF 1.65 //参考电压偏置
#define Voffset 1.605 //偏置电压大小
#define AM_GAIN 4.86 //运放放大倍数
#define R_SENSE 0.02 //Ω 采样电阻
#define V_GAIN 21 //(100+100+10)/10
void GetADCvaule(){
float SUM;
current.ADC_Ia = (int16_t)ADC_GetValue(CURR_CHANNEL_U);
current.ADC_Ib = (int16_t)ADC_GetValue(CURR_CHANNEL_V);
current.ADC_Ibus = (int16_t)ADC_GetValue(IBUS_CHANNEL);
current.Ibus = (ADC_V_K*(current.ADC_Ibus-current.ADC_Ibus_offset) - Voffset) / (AM_GAIN * R_SENSE);
SUM = current.Ibus * R_SENSE;
current.Ia = (ADC_V_K*(current.ADC_Ia-current.ADC_Ia_offset) - Voffset + 4.84*SUM) / (AM_GAIN * R_SENSE);
current.Ib = (ADC_V_K*(current.ADC_Ib-current.ADC_Ib_offset) - Voffset + 4.84*SUM) / (AM_GAIN * R_SENSE);
// current.Ia = (ADC_V_K*(current.ADC_Ia-current.ADC_Ia_offset) - Voffset) / (AM_GAIN * R_SENSE);
// current.Ib = (ADC_V_K*(current.ADC_Ib-current.ADC_Ib_offset) - Voffset) / (AM_GAIN * R_SENSE);
current.Ic = current.Ibus - current.Ia - current.Ib;
current.ADC_Vbus = (int16_t)ADC_GetValue(VDC_CHANNEL);
current.Vbus = ADC_V_K*(current.ADC_Vbus) * V_GAIN;
current.ADC_Handle = (int16_t)ADC_GetValue(Handle_CHANNEL);
current.VHandle = ADC_V_K*(current.ADC_Handle); //0-3.3的范围
}
对电机进行foc控制,将ADC电流波形通过串口打印出来。测试了几组,稳定时波形差不多是这样。
2.2 情况二:U、V两相的运放反相输入端是GND
计算公式如下:
实际执行上述代码的注释部分,求Ia和Ib。
再次实验采样三相电流,打印输出。波形差不多。
3.结论
SUM为Ibus*0.02,由于当前的转速较慢,SUM值较小,未能根据波形判断出反相端 接入情况。后续将继续调试完成高转速下的波形判断,当Ibus电流变大,两种接入情况将一目了然。
- 2024-12-24
-
发表了主题帖:
【MCXA156开发板测评】环境配置
1.上手体验
MCXA156采用Arm® Cortex® M33内核,其中运行频率高达96MHz。开发板实物颜值极高,layout布局优雅。在硬件资源上,板载 CMSIS-DAP 减少了外部连线,同时方便调试。
2. ide下载
对恩智浦资料的下载需要注册一个账号。进入官网注册遇到了很大问题,使用了三个邮箱(qq、163、edu)都不太能实时收到注册邮件。等收到邮件的时候,验证码已经过期了。这里我是给官方提单反馈,完成了注册(看别人的贴子,好像谷歌邮箱可以正常注册)。
第一次使用恩智浦的MCU,当然要体验恩智浦完整的开发环境,所以选择MCUXpresso IDE作为编译工具。在官网下载MCUXpresso IDE for NXP MCUs | Linux, Windows and MacOS | NXP Semiconductors | NXP Semiconductors
有Linux、macos、windows、arm64多个平台的安装包,选择对应的默认安装即可。
3. 环境配置
进入IDE之后,选择Import SDK example
在弹出的界面 点击NXP IDE的小标志
在新界面搜索A156,选择对应的开发板即可安装SDK包
4. 例程测试
同上Import SDK example,选择A156开发板,就开到了全部例程,这里选择点灯程序,
点击编译、连接数据线到 CMSIS-DAP接口,点击debug按钮即可完成下载。
灯的闪烁速度比初始更快
[localvideo]9f665b6715c5ff6120657dc7e51cfa0e[/localvideo]
- 2024-12-19
-
回复了主题帖:
测评入围名单: NXP MCX A系列 FRDM-MCXA156开发板
个人信息无误,确认可以完成测评分享计划
- 2024-12-17
-
发表了主题帖:
【极海APM32M3514电机通用评估板】外设篇(求助ADC)
本帖最后由 不语arc 于 2024-12-17 17:02 编辑
0.前提
官方例程是使用滑模观测器的无感FOC控制,但我无法测得自己的电机的各项参数,调试滑模观测器参数始终无法让电机转动起来(求大佬指点)。所以后续我打算直接使用有感FOC完成对电机的控制。
极海lib中的算法相当于使用uint类型完成浮点计算,也就是标幺化。但使用官方api完成有感控制,需要做大量的移植工作,而且有难度。因此,计划先使用math库,完成有感foc控制。在本篇,我将先完成对各个外设的基本驱动。
1.usart
串口配置,参考:【极海APM32M3514电机通用评估板】 串口输出测试 - 国产芯片交流 - 电子工程世界-论坛
将swd复用为串口,在硬件上与ch340模块的TX管脚相连(用不到RX),这里我使用了波动开关。
swd连ch340时,在main.c中开头加一段延时,如此可以在这个时间中继续使用下载功能。不连ch340时,swd仍可以用于调试,需将初始化USART的代码注释掉。
上位机vofa+用于显示波形非常方便,这里使用JustFloat发送格式,只需要在包尾发送对应的tail[4]{0x00,0x00,0x80,0x7f}即可上传多个通道的数据。
发送函数:
//本协议是纯十六进制浮点传输
void vofa_JustFloat_output(float s1,float s2,float s3,float s4)
{
float data[4];
uint8_t tail[4] = {0x00, 0x00, 0x80, 0x7f};
// 发送数据
data[0] = s1;
data[1] = s2;
data[2] = s3;
data[3] = s4;
Usart_SendData(MINI_COM1,(uint8_t*)data,sizeof(float) * 4); //发送数据
// 发送帧尾
Usart_SendData(MINI_COM1,tail,4);
}
2.PWM
本身的pwm配置已经配置好了,宏定义如下。pwm时钟频率同样为72Mhz,计数采用中心对齐模式,pwm开关频率为16khz。
3. I2C编码器
见上一篇分享
4.ADC
五路ADC
其中W相电流 = Ibus - Iu - Iv
配置方式不变,注意右对齐也就是低12位存储读取的结果。
将读取结果转成实际值,注意这里的ADC_Ia_offset为电机未转动时,读取的a相偏置电流大小。
实际读取的值是运放 输出的结果,对其进行解算,得到实际值。
typedef struct
{
float Ia; // Phase A current
float Ib; // Phase B current
float Ic; // Phase C current
float Ibus;
float Vbus;
float VHandle; //0-3.3的范围
uint16_t ADC_Ia; // Phase A Voltage(I*R)
uint16_t ADC_Ib; // Phase B Voltage
uint16_t ADC_Ibus; // SUM Voltage
uint16_t ADC_Ia_offset; // 电流零点偏置
uint16_t ADC_Ib_offset; // 电流零点偏置
uint16_t ADC_Ibus_offset; // 电流零点偏置
uint16_t ADC_Vbus; // Phase bus Voltage
uint16_t ADC_Handle;
} CURRENT_Def;
#define ADC_V_K 0.0008 // 3.3/4096.0 // ADC 与电压转换系数
#define VREF 1.65 //参考电压偏置
#define AM_GAIN 4.86 //运放放大倍数
#define R_SENSE 0.02 //Ω 采样电阻
#define V_GAIN 21 //(100+100+10)/10
void GetADCvaule(){
current.ADC_Ia = (int16_t)ADC_GetValue(CURR_CHANNEL_U);
current.ADC_Ib = (int16_t)ADC_GetValue(CURR_CHANNEL_V);
current.ADC_Ibus = (int16_t)ADC_GetValue(IBUS_CHANNEL);
current.Ia = (ADC_V_K*(current.ADC_Ia-current.ADC_Ia_offset) - VREF) / (AM_GAIN * R_SENSE);
current.Ib = (ADC_V_K*(current.ADC_Ib-current.ADC_Ib_offset) - VREF) / (AM_GAIN * R_SENSE);
current.Ibus = (ADC_V_K*(current.ADC_Ibus-current.ADC_Ibus_offset) - VREF) / (AM_GAIN * R_SENSE);
current.Ic = current.Ibus - current.Ia - current.Ib;
current.ADC_Vbus = (int16_t)ADC_GetValue(VDC_CHANNEL);
current.Vbus = ADC_V_K*(current.ADC_Vbus) * V_GAIN;
current.ADC_Handle = (int16_t)ADC_GetValue(Handle_CHANNEL);
current.VHandle = ADC_V_K*(current.ADC_Handle); //0-3.3的范围
}
在这里提一个问题:为什么U、V两相的运放反相输入端为什么是实际的SUM?计算出的公式,是按照反相输入端接地计算得到的,虽然SUM值不算大。
另外,ADC的中断处理函数好像存在bug,为什么会发生循环执行无法退出ADC中断的情况。具体表现为ADC->STS 无法幅值,中断标志位无法清零。(关闭了看门狗)
两处写法很奇怪,但官方例程居然不怎么出现这个bug