项目介绍
基于NXP i.MX RT1021实现的日历,使用240*240的oled全彩小屏幕显示,采用lvgl作为显示驱动。
项目设计思路(含设计框图)
- 最简板子:板上无其他多余外设,大部分为最小系统模块,仅留出一个用户LED灯与复位按键。
- 最全接口:所有IO都通过排针引出,都可直接使用。
- 最易使用:板载DAP Link,支持调试、下载、串口、供电多种功能,一线通。
- 最小体积:主要使用0402封装器件,双面布局布线,四层板设计,减小体积。
芯片介绍
i.MX RT1021跨界MCU是EdgeVerse™ 边缘计算平台的一部分,通过低成本LQFP封装提供高性能功能集,进一步为客户简化板设计与布局。i.MX RT1021 MCU基于Arm® Cortex®-M7内核,运行频率500 MHz。
RT1020系列芯片的特征:
- 高性能Arm Cortex-M7内核
- 2517 CoreMark/1070 DMIPS @ 500 MHz
- 实时低延迟响应,低至20 ns
- 高达256 kB紧耦合存储器(TCM)
- 通过集成DC-DC转换器实现行业最低的动态功耗
- 低功耗运行模式下运行频率为24MHz
- 丰富的外部存储接口选项
- NAND、eMMC、QuadSPI NOR Flash和Parallel NOR Flash
- 无线连接接口:
- Wi-Fi®、蓝牙、低功耗蓝牙、ZigBee®和Thread™
- 面向低成本PCB设计的144LQFP和100LQFP封装
搜集素材的思路
主要通过NXP官网获取需要资料。
参考官方RT1020板卡原理图,进行相应改动去实验。
画原理图、PCB制板过程中遇到的问题,以及解决方法
一切顺利,没碰见啥问题。主要注意以下设计:
1. 电源引脚和上电时序
对于有VSNVS低功耗需求,需要1个单独的LDO+1个DCDC,上电顺序上需满足VSNVS先于VDD_HIGH_IN和DCDC_IN上电。
对于无VSNVS低功耗需求,可以单电源LDO供电,上电顺序要保证VSNVS与VDD_HIGH_IN和DCDC_IN同时上电。
2.代码启动配置引脚
BOOT_MODE[0:1] (需要留出来作为启动模式选择,外加拨码开关或者电阻,如果是作为复用引脚,需要注意外部上下拉的影响可能导致芯片无法启动)
BOOT_CFG1[0:7]和BOOT_CFG2[0:3](这10个Pin和EMC复用,根据启动配置固定上下拉即可)
对于常用的QSPI启动来说,都是采用默认的0即可,契合内部的下拉电阻,只要外部没有特别的上拉,问题不大。在调试阶段,建议使用拨码开关或者Jumper跳针作为启动选择。后期量产直接就可以Fuse来设定启动模式了,这些引脚可以继续作为其它功能。
3. 时钟输入引脚
RT芯片需要外置RTC32K和24M无源时钟,但是如果客户不需要RTC准确的定时,可以省去RTC 32K,处理方式是RTC_XTALI连接到GND,RTC_XTALO直接Floating。
4. LDO和DCDC输出能力选择
RT中的电流需求是DCDC_IN, VDD_High_IN, VSNVS_IN以及外设的电流之和,最大值200mA+GPIO电流.
5. DQS引脚设定
为最大程度的提高Flexspi执行速度,如果Flexspi没有DQS引脚,建议FLEXSPI_A_DQS(GPIO_SD_B1_05)引脚Floating,否则只能到60M。
实现结果展示(调试过程中遇到什么问题)
板卡展示
日历界面展示
调试的过程中碰到屏幕无法电亮的问题,用示波器量了一下发现有IO虚焊,补焊后正常。
关键代码及说明
主代码
#define LPSPI_MASTER_DMA_BASEADDR DMA0
#define LPSPI_MASTER_DMA_MUX_BASEADDR DMAMUX
static volatile bool s_lvgl_initialized = false;
#include "gui_guider.h"
/*******************************************************************************
* Prototypes
******************************************************************************/
#if LV_USE_LOG
static void print_cb(const char *buf)
{
PRINTF("\r%s\n", buf);
}
#endif
extern void setup_scr_screen(lv_ui *ui);
lv_ui ui;
static void AppTask(void *param)
{
#if LV_USE_LOG
lv_log_register_print_cb(print_cb);
#endif
PRINTF("lvgl widgets demo started\r\n");
lv_port_pre_init();
lv_init();
lv_port_disp_init();
lv_port_indev_init();
s_lvgl_initialized = true;
setup_ui(&ui);
for (;;)
{
lv_task_handler();
vTaskDelay(5);
}
}
/*******************************************************************************
* Code
******************************************************************************/
/*!
* @brief Main function
*/
int main(void)
{
BaseType_t stat;
gpio_pin_config_t gpio_out_config = {kGPIO_DigitalOutput, 0, kGPIO_NoIntmode};
/* Init board hardware. */
BOARD_ConfigMPU();
BOARD_InitPins();
BOARD_BootClockRUN();
BOARD_InitDebugConsole();
GPIO_PinInit(BOARD_INITPINS_LCD_RES_GPIO, BOARD_INITPINS_LCD_RES_GPIO_PIN, &gpio_out_config);
GPIO_PinWrite(BOARD_INITPINS_LCD_RES_GPIO, BOARD_INITPINS_LCD_RES_GPIO_PIN, 1U);
GPIO_PinInit(BOARD_INITPINS_LCD_DC_GPIO, BOARD_INITPINS_LCD_DC_GPIO_PIN, &gpio_out_config);
GPIO_PinWrite(BOARD_INITPINS_LCD_DC_GPIO, BOARD_INITPINS_LCD_DC_GPIO_PIN, 1U);
/* DMA Mux init and EDMA init */
edma_config_t edmaConfig = {0};
EDMA_GetDefaultConfig(&edmaConfig);
EDMA_Init(LPSPI_MASTER_DMA_BASEADDR, &edmaConfig);
#if (defined(FSL_FEATURE_SOC_DMAMUX_COUNT) && FSL_FEATURE_SOC_DMAMUX_COUNT)
DMAMUX_Init(LPSPI_MASTER_DMA_MUX_BASEADDR);
#endif
stat = xTaskCreate(AppTask, "lvgl", configMINIMAL_STACK_SIZE + 800, NULL, tskIDLE_PRIORITY + 2, NULL);
if (pdPASS != stat)
{
PRINTF("Failed to create lvgl task");
while (1)
;
}
vTaskStartScheduler();
for (;;)
{
} /* should never get here */
}
/*!
* @brief Malloc failed hook.
*/
void vApplicationMallocFailedHook(void)
{
PRINTF("Malloc failed. Increase the heap size.");
for (;;)
;
}
/*!
* @brief FreeRTOS tick hook.
*/
void vApplicationTickHook(void)
{
if (s_lvgl_initialized)
{
lv_tick_inc(1);
}
}
/*!
* @brief Stack overflow hook.
*/
void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName)
{
(void)pcTaskName;
(void)xTask;
for (;;)
;
}
LVGL显示部分
static void screen_calendar_1draw_part_begin_event_cb(lv_event_t * e)
{
lv_obj_t * obj = lv_event_get_target(e);
lv_obj_draw_part_dsc_t * dsc = lv_event_get_param(e);
if(dsc->part == LV_PART_ITEMS) {
if(dsc->id < 7) {
dsc->label_dsc->color = lv_color_make(0x0D, 0x30, 0x55);
dsc->label_dsc->font = &lv_font_montserratMedium_12;
} else if (lv_btnmatrix_has_btn_ctrl(obj, dsc->id, LV_BTNMATRIX_CTRL_DISABLED)) {
dsc->rect_dsc->bg_color = lv_color_make(0x01, 0xa2, 0xb1);
dsc->rect_dsc->bg_grad.stops_count = 2;
dsc->rect_dsc->bg_grad.dir = LV_GRAD_DIR_NONE;
dsc->rect_dsc->bg_grad.stops[0].color = lv_color_make(0x01, 0xa2, 0xb1);
dsc->rect_dsc->bg_grad.stops[1].color = lv_color_make(0x01, 0xa2, 0xb1);
dsc->rect_dsc->bg_opa = 0;
dsc->label_dsc->color = lv_color_make(0xA9, 0xA2, 0xA2);
dsc->label_dsc->font = &lv_font_montserratMedium_12;
} else if(lv_btnmatrix_has_btn_ctrl(obj, dsc->id, LV_BTNMATRIX_CTRL_CUSTOM_1)) {
dsc->rect_dsc->bg_color = lv_color_make(0x01, 0xa2, 0xb1);
dsc->rect_dsc->bg_grad.stops_count = 2;
dsc->rect_dsc->bg_grad.dir = LV_GRAD_DIR_NONE;
dsc->rect_dsc->bg_grad.stops[0].color = lv_color_make(0x01, 0xa2, 0xb1);
dsc->rect_dsc->bg_grad.stops[1].color = lv_color_make(0x01, 0xa2, 0xb1);
dsc->rect_dsc->bg_opa = 255;
dsc->rect_dsc->border_color = lv_color_make(0xc0, 0xc0, 0xc0);
dsc->rect_dsc->border_width = 1;
dsc->rect_dsc->border_opa = 255;
dsc->label_dsc->color = lv_color_make(0x0D, 0x30, 0x55);
dsc->label_dsc->font = &lv_font_montserratMedium_12;
}
else if(lv_btnmatrix_has_btn_ctrl(obj, dsc->id, LV_BTNMATRIX_CTRL_CUSTOM_2)) {
dsc->rect_dsc->bg_color = lv_color_make(0xf0, 0x00, 0x00);
dsc->rect_dsc->bg_grad.stops_count = 2;
dsc->rect_dsc->bg_grad.dir = LV_GRAD_DIR_NONE;
dsc->rect_dsc->bg_grad.stops[0].color = lv_color_make(0xf0, 0x00, 0x00);
dsc->rect_dsc->bg_grad.stops[1].color = lv_color_make(0x21, 0x95, 0xf6);
dsc->rect_dsc->bg_opa = 0;
dsc->label_dsc->color = lv_color_make(0x0D, 0x30, 0x55);
dsc->label_dsc->font = &lv_font_montserratMedium_12;
} else {
dsc->rect_dsc->bg_color = lv_color_make(0x01, 0xa2, 0xb1);
dsc->rect_dsc->bg_grad.stops_count = 2;
dsc->rect_dsc->bg_grad.dir = LV_GRAD_DIR_NONE;
dsc->rect_dsc->bg_grad.stops[0].color = lv_color_make(0x01, 0xa2, 0xb1);
dsc->rect_dsc->bg_grad.stops[1].color = lv_color_make(0x01, 0xa2, 0xb1);
dsc->rect_dsc->bg_opa = 0;
dsc->rect_dsc->border_color = lv_color_make(0xc0, 0xc0, 0xc0);
dsc->rect_dsc->border_width = 1;
dsc->rect_dsc->border_opa = 255;
dsc->label_dsc->color = lv_color_make(0x0D, 0x30, 0x55);
dsc->label_dsc->font = &lv_font_montserratMedium_12;
}
}
}
static lv_calendar_date_t date;
static void screen_calendar_1_event_handler(lv_event_t * e)
{
lv_event_code_t code = lv_event_get_code(e);
if (code == LV_EVENT_VALUE_CHANGED) {
lv_obj_t * obj =lv_event_get_current_target(e);
lv_calendar_get_pressed_date(obj,&date);
lv_calendar_set_highlighted_dates(obj, &date, 1);
}
}
static lv_obj_t * g_kb_screen;
static void kb_screen_event_cb(lv_event_t *e)
{
lv_event_code_t code = lv_event_get_code(e);
lv_obj_t *kb = lv_event_get_target(e);
if(code == LV_EVENT_READY || code == LV_EVENT_CANCEL){
lv_obj_add_flag(kb, LV_OBJ_FLAG_HIDDEN);
}
}
__attribute__((unused)) static void ta_screen_event_cb(lv_event_t *e)
{
lv_event_code_t code = lv_event_get_code(e);
lv_obj_t *ta = lv_event_get_target(e);
lv_obj_t *kb = lv_event_get_user_data(e);
if (code == LV_EVENT_FOCUSED || code == LV_EVENT_CLICKED)
{
lv_keyboard_set_textarea(kb, ta);
lv_obj_move_foreground(kb);
lv_obj_clear_flag(kb, LV_OBJ_FLAG_HIDDEN);
}
if (code == LV_EVENT_CANCEL || code == LV_EVENT_DEFOCUSED)
{
lv_keyboard_set_textarea(kb, NULL);
lv_obj_move_background(kb);
lv_obj_add_flag(kb, LV_OBJ_FLAG_HIDDEN);
}
}
void setup_scr_screen(lv_ui *ui){
//Write codes screen
ui->screen = lv_obj_create(NULL);
//Create keyboard on screen
g_kb_screen = lv_keyboard_create(ui->screen);
lv_obj_add_event_cb(g_kb_screen, kb_screen_event_cb, LV_EVENT_ALL, NULL);
lv_obj_add_flag(g_kb_screen, LV_OBJ_FLAG_HIDDEN);
lv_obj_set_style_text_font(g_kb_screen, &lv_font_simsun_18, LV_PART_MAIN|LV_STATE_DEFAULT);
lv_obj_set_scrollbar_mode(ui->screen, LV_SCROLLBAR_MODE_OFF);
//Set style for screen. Part: LV_PART_MAIN, State: LV_STATE_DEFAULT
lv_obj_set_style_bg_color(ui->screen, lv_color_make(0xff, 0xff, 0xff), LV_PART_MAIN|LV_STATE_DEFAULT);
lv_obj_set_style_bg_grad_color(ui->screen, lv_color_make(0x21, 0x95, 0xf6), LV_PART_MAIN|LV_STATE_DEFAULT);
lv_obj_set_style_bg_grad_dir(ui->screen, LV_GRAD_DIR_NONE, LV_PART_MAIN|LV_STATE_DEFAULT);
lv_obj_set_style_bg_opa(ui->screen, 0, LV_PART_MAIN|LV_STATE_DEFAULT);
//Write codes screen_calendar_1
ui->screen_calendar_1 = lv_calendar_create(ui->screen);
lv_obj_set_pos(ui->screen_calendar_1, 0, 0);
lv_obj_set_size(ui->screen_calendar_1, 240, 240);
lv_obj_set_scrollbar_mode(ui->screen_calendar_1, LV_SCROLLBAR_MODE_OFF);
today.year = 2023;
today.month = 7;
today.day = 23;
lv_calendar_set_today_date(ui->screen_calendar_1, today.year, today.month, today.day);
lv_calendar_set_showed_date(ui->screen_calendar_1, today.year, today.month);
highlihted_days[0].year = 2023;
highlihted_days[0].month = 7;
highlihted_days[0].day = 24;
lv_calendar_set_highlighted_dates(ui->screen_calendar_1, highlihted_days, 1);
lv_obj_t *screen_calendar_1_header = lv_calendar_header_arrow_create(ui->screen_calendar_1);
lv_calendar_t * screen_calendar_1 = (lv_calendar_t *)ui->screen_calendar_1;
lv_obj_add_event_cb(screen_calendar_1->btnm, screen_calendar_1draw_part_begin_event_cb, LV_EVENT_DRAW_PART_BEGIN, NULL);
lv_obj_add_event_cb(ui->screen_calendar_1, screen_calendar_1_event_handler, LV_EVENT_ALL, NULL);
//Set style for screen_calendar_1. Part: LV_PART_MAIN, State: LV_STATE_DEFAULT
lv_obj_set_style_radius(ui->screen_calendar_1, 0, LV_PART_MAIN|LV_STATE_DEFAULT);
lv_obj_set_style_bg_color(ui->screen_calendar_1, lv_color_make(0xff, 0xff, 0xff), LV_PART_MAIN|LV_STATE_DEFAULT);
lv_obj_set_style_bg_grad_color(ui->screen_calendar_1, lv_color_make(0xff, 0xff, 0xff), LV_PART_MAIN|LV_STATE_DEFAULT);
lv_obj_set_style_bg_grad_dir(ui->screen_calendar_1, LV_GRAD_DIR_NONE, LV_PART_MAIN|LV_STATE_DEFAULT);
lv_obj_set_style_bg_opa(ui->screen_calendar_1, 255, LV_PART_MAIN|LV_STATE_DEFAULT);
lv_obj_set_style_shadow_width(ui->screen_calendar_1, 0, LV_PART_MAIN|LV_STATE_DEFAULT);
lv_obj_set_style_shadow_color(ui->screen_calendar_1, lv_color_make(0x21, 0x95, 0xf6), LV_PART_MAIN|LV_STATE_DEFAULT);
lv_obj_set_style_shadow_opa(ui->screen_calendar_1, 255, LV_PART_MAIN|LV_STATE_DEFAULT);
lv_obj_set_style_shadow_spread(ui->screen_calendar_1, 0, LV_PART_MAIN|LV_STATE_DEFAULT);
lv_obj_set_style_shadow_ofs_x(ui->screen_calendar_1, 0, LV_PART_MAIN|LV_STATE_DEFAULT);
lv_obj_set_style_shadow_ofs_y(ui->screen_calendar_1, 0, LV_PART_MAIN|LV_STATE_DEFAULT);
lv_obj_set_style_border_color(ui->screen_calendar_1, lv_color_make(0xc0, 0xc0, 0xc0), LV_PART_MAIN|LV_STATE_DEFAULT);
lv_obj_set_style_border_width(ui->screen_calendar_1, 1, LV_PART_MAIN|LV_STATE_DEFAULT);
lv_obj_set_style_border_opa(ui->screen_calendar_1, 255, LV_PART_MAIN|LV_STATE_DEFAULT);
//Set style state: LV_STATE_DEFAULT for style_screen_calendar_1_extra_header_main_default
static lv_style_t style_screen_calendar_1_extra_header_main_default;
ui_init_style(&style_screen_calendar_1_extra_header_main_default);
lv_style_set_bg_color(&style_screen_calendar_1_extra_header_main_default, lv_color_make(0x21, 0x95, 0xf6));
lv_style_set_bg_grad_color(&style_screen_calendar_1_extra_header_main_default, lv_color_make(0x21, 0x95, 0xf6));
lv_style_set_bg_grad_dir(&style_screen_calendar_1_extra_header_main_default, LV_GRAD_DIR_NONE);
lv_style_set_bg_opa(&style_screen_calendar_1_extra_header_main_default, 255);
lv_style_set_text_color(&style_screen_calendar_1_extra_header_main_default, lv_color_make(0xff, 0xff, 0xff));
lv_style_set_text_font(&style_screen_calendar_1_extra_header_main_default, &lv_font_montserratMedium_12);
lv_obj_add_style(screen_calendar_1_header, &style_screen_calendar_1_extra_header_main_default, LV_PART_MAIN|LV_STATE_DEFAULT);
}
OLED驱动
看似代码是FT9341,其实是ST7789,懒得改了。
void FT9341_Init(ili9341_send_byte_t _writeData, ili9341_send_byte_t _writeCommand)
{
GPIO_PinWrite(BOARD_INITPINS_LCD_RES_GPIO, BOARD_INITPINS_LCD_RES_GPIO_PIN, 0U);
SDK_DelayAtLeastUs(300 * 1000U, SDK_DEVICE_MAXIMUM_CPU_CLOCK_FREQUENCY);
GPIO_PinWrite(BOARD_INITPINS_LCD_RES_GPIO, BOARD_INITPINS_LCD_RES_GPIO_PIN, 1U);
SDK_DelayAtLeastUs(300 * 1000U, SDK_DEVICE_MAXIMUM_CPU_CLOCK_FREQUENCY);
_writeCommand(0x36);
_writeData(0x00);
_writeCommand(0x3A);
_writeData(0x05);
_writeCommand(0xB2);
_writeData(0x0C);
_writeData(0x0C);
_writeData(0x00);
_writeData(0x33);
_writeData(0x33);
_writeCommand(0xB7);
_writeData(0x35);
_writeCommand(0xBB);
_writeData(0x19);
_writeCommand(0xC0);
_writeData(0x2C);
_writeCommand(0xC2);
_writeData(0x01);
_writeCommand(0xC3);
_writeData(0x12);
_writeCommand(0xC4);
_writeData(0x20);
_writeCommand(0xC6);
_writeData(0x0F);
_writeCommand(0xD0);
_writeData(0xA4);
_writeData(0xA1);
_writeCommand(0xE0);
_writeData(0xD0);
_writeData(0x04);
_writeData(0x0D);
_writeData(0x11);
_writeData(0x13);
_writeData(0x2B);
_writeData(0x3F);
_writeData(0x54);
_writeData(0x4C);
_writeData(0x18);
_writeData(0x0D);
_writeData(0x0B);
_writeData(0x1F);
_writeData(0x23);
_writeCommand(0xE1);
_writeData(0xD0);
_writeData(0x04);
_writeData(0x0C);
_writeData(0x11);
_writeData(0x13);
_writeData(0x2C);
_writeData(0x3F);
_writeData(0x44);
_writeData(0x51);
_writeData(0x2F);
_writeData(0x1F);
_writeData(0x1F);
_writeData(0x20);
_writeData(0x23);
_writeCommand(0x21);
_writeCommand(0x11);
_writeCommand(0x29);
}
在芯片设计过程中,遇到什么难题以及解决方法,或未来针对这个芯片的扩展项目
没碰见啥问题。
计划后期设计一块扩展板,板载屏幕,按键,摇杆,加速度,温湿度等外设,作为一个综合学习板。
芯片的优势与局限
- NXP的i.MX RT系列的低价格策略确实很是吸引人,但是由于芯片没有内置flash,应用起来还是自己外接Flash,成本会相应增加,应用方便性也不如内置flash方便。
- 电路设计比较复杂,没有片上的FLash,电源需要上电下电时序,要设计启动方式配置等等。
- 性能强大。
- 可用IO数量少。
原理图/PCB图
见附件
可编译下载的代码
见附件