一、硬件介绍:
FRDM-MCXN236开发板是来自NXP 一款紧凑型、可扩展的高性价比开发板,专为基于 MCX N23x 系列微控制器的快速原型设计而打造。该开发板的核心是一颗 MCX N236 微控制器,搭载 Arm Cortex-M33 内核,主频最高可达 150 MHz,配备 1MB 双组闪存(支持完整 ECC RAM)和 352KB ECC RAM,并集成 SmartDMA 加速器,能够高效地将工作负载分配到模拟与数字外设上。
二、任务选择:
这次任务,任务3:使用 mikroBUS 接口规范,设计一款扩展板,扩展板功能类型不限,可以是通信、显示、传感器、存储、控制等功能模块。按任务要求,我这里使用 mikroBUS 接口制作了一块DS3232的时钟芯片模块。
三、设计思路:
手头有DS3232时钟芯片,这是一款高精度 I²C 实时时钟(RTC)芯片,带温度补偿晶振(TCXO)的 RTC,内置电池备电、256字节 SRAM、两个可编程闹钟、±2ppm 精度。这里使用mikroBUS 接口制作成模块,外接电池,可以确保整个模块取下后依然准确走时。




DS3232芯片连接到mikroBUS 接口上的I2C总线上,使用3.3V供电。在mikroBUS接口的MOSI引脚上额外连接了两颗WS2812的LED,不过本项目中没有使用。

模块焊接好后,直接插在FRDM-MCXN236开发板的mikroBUS 接口上。FRDM-MCXN236开发板通过I2C总线与DS3232通讯,读取当前时间和设置时间、闹铃。上位机通过串口与FRDM-MCXN236通讯,获取RTC的时间,和设置闹铃。系统框图如下:

四、任务实现:
项目开发分两部分,一个是上位机,使用python开发;一个是下位机,使用官方提供的MCUXpressoIDE开发工具。
上位机使用串口与单片机通讯,使用QT做了个简单的界面。显示当前时间,并且能设置两个闹铃。

在上位机打开串口时,会尝试通过互联网获取当前时间,然后通过串口下发给下位机用来校正时间。可以设定两个闹铃,闹铃可以设定小时、分钟,每天重复。
下位机使用C语言开发,使用官方例程“frdmmcxn236_lpi2c_edma_b2b_transfer_master”进行修改,保留了例程中的I2C通讯部分,去除了DMA部分功能。增加了串口输入的部分。

/*!
* @brief 初始化 LPUART4(板载 USB 虚拟串口)
*/
static void LPUART4_Init(void) {
lpuart_config_t config;
LPUART_GetDefaultConfig(&config);
config.baudRate_Bps = 115200;
config.enableTx = true;
config.enableRx = true;
LPUART_Init(LPUART4, &config, CLOCK_GetLPFlexCommClkFreq(4U));
}
使用轮询的方式处理串口输入。对设置时间和设置闹铃,使用串口接收字符的第一个字符进行区分,0XAA就是设置时间,0XAE就是设置闹铃。
/*!
* @brief 串口校时状态机处理(复用板载 USB 虚拟串口 LPUART4)
* @return true if a time sync just happened
*/
static bool CheckAndHandleTimeSync(LPI2C_Type *i2cBase) {
while (LPUART_GetStatusFlags(DEMO_LPUART) & kLPUART_RxDataRegFullFlag) {
uint8_t byte = LPUART_ReadByte(DEMO_LPUART);
/* 检测帧头 */
if (byte == SYNC_HEADER_1) {
g_protoType = SYNC_HEADER_1;
g_syncIdx = 0;
continue;
}
if (byte == ALARM_HEADER) {
g_protoType = ALARM_HEADER;
g_syncIdx = 0;
continue;
}
/* 校时数据接收:AA + 7字节 */
if (g_protoType == SYNC_HEADER_1) {
g_syncBuf[g_syncIdx++] = byte;
if (g_syncIdx >= SYNC_DATA_LEN) {
DS3232_WriteTime(i2cBase, g_syncBuf[0], g_syncBuf[1],
g_syncBuf[2], g_syncBuf[3], g_syncBuf[4], g_syncBuf[5],
g_syncBuf[6]);
g_protoType = 0;
g_syncIdx = 0;
return true;
}
}
/* 闹钟数据接收:AE + 4字节 */
else if (g_protoType == ALARM_HEADER) {
g_syncBuf[g_syncIdx++] = byte;
if (g_syncIdx >= ALARM_DATA_LEN) {
uint8_t hour1_en = g_syncBuf[0];
uint8_t min1 = g_syncBuf[1];
uint8_t hour2_en = g_syncBuf[2];
uint8_t min2 = g_syncBuf[3];
bool alarm1_en = (hour1_en & 0x80) ? false : true;
bool alarm2_en = (hour2_en & 0x80) ? false : true;
uint8_t hour1 = hour1_en & 0x7F;
uint8_t hour2 = hour2_en & 0x7F;
rtc_time_t now;
DS3232_ReadTime(i2cBase, &now);
if (alarm1_en) {
DS3232_SetAlarm1Daily(i2cBase, hour1, min1, now.wday);
}
if (alarm2_en) {
DS3232_SetAlarm2Daily(i2cBase, hour2, min2, now.wday);
}
DS3232_EnableAlarmInt(i2cBase, alarm1_en, alarm2_en);
g_protoType = 0;
g_syncIdx = 0;
return true;
}
}
}
return false;
}
模块设计没有将DS3232的闹铃中断引脚引出,所以当闹铃事件发生时就只能通过查询寄存器的方式获得。当发生闹铃,就闪烁板子上的红色LED灯进行提醒。
while (1) {
/* 优先处理串口校时 */
CheckAndHandleTimeSync(EXAMPLE_I2C_MASTER);
// SDK_DelayAtLeastUs(1000000, SystemCoreClock);
SDK_DelayAtLeastUs(250000, SystemCoreClock);
ret = DS3232_ReadTime(EXAMPLE_I2C_MASTER, &now);
if (ret != kStatus_Success) {
PRINTF("I2C read error!\r\n");
continue;
}
print_time(&now);
DS3232_ReadReg(EXAMPLE_I2C_MASTER, DS3232_REG_STATUS, &status_reg);
if (status_reg & (STATUS_A1F | STATUS_A2F)) {
/* 清标志 */
alarm=1;
status_reg &= ~(STATUS_A1F | STATUS_A2F);
DS3232_WriteReg(EXAMPLE_I2C_MASTER, DS3232_REG_STATUS, status_reg);
}
if(alarm>0){
GPIO_PortToggle(LED_PORT, 1U << LED_RED_PIN);
alarm++;
if(alarm>200) alarm=0;
}else{
RGB_LED_SetColor(false, false, false);
}
}
五、效果展示:
开机后,上位机通过串口读取DS3232当前的时间。


设置两个闹铃,到了闹铃时间,板子上红灯闪烁,提示闹铃。


六、心得体会:
感谢得捷和硬禾科技。一直想动手制作一块mikroBUS接口的板子,终于借此机会实现了。有一点点小小缺憾,自制的mikroBUS接口模块,没有将DS3232的闹钟中断引脚引出,无法通过中断判断闹铃的发生。但是主要功能都实现了,还是非常开心。