发新帖本帖赏金 100.00元(功能说明)我要提问
返回列表

[MM32生态] 【MM32+模块】系列:01、开篇(软硬件准备)

[复制链接]
4477|10
手机看帖
扫描二维码
随时随地手机跟帖
xld0932|  楼主 | 2022-3-20 08:41 | 显示全部楼层 |阅读模式
本帖最后由 xld0932 于 2022-3-20 08:43 编辑

#申请原创#   @21小跑堂

前面分享了几篇基于MM32的应用得到了21ic及网友们的鼓励及支持,后续还有更多的分享,但由于都是基于实物设计方案的分享,需要设计原理图、进行PCB布线、打板焊接调试,以及功能程序设计等步骤,更新进度相对会慢一些;所以准备了MM32 + 模块这个系列的分享,可以通过MM32的核心板结合众多的硬件功能模块来实现一个简单的单一功能,后期也能通过这种搭积木的方式,来完成一个复杂的项目。所以在开始这个系列主题帖之前,我们需要准备一些资料、硬件环境、软件环境,以及调试工具、基础工程等;这些我们在接下来的内容会一一讲到:

芯片资料
我们这个系列主题会以灵动微电子的MM32F014CD6P这个芯片作为主控芯片,虽然它是一颗Arm Cortex-M0内核的32MCU,但它最高工作频率可以达到72MHz,与STM32F103系列的工作频率处于同一水平了;存储部分带有了64KBFLASH8KBSRAM,满足了大多数应用场景下对于应用功能程序空间大小的要求;另外其本身还具备了丰富的外设功能,包含了112位的ADC1个比较器、116位高级定时器、116位和132位通用定时器、316位基本定时器,还包含了标准的通信接口:1I2C接口、2SPII2S接口、3UART接口和FlexCAN接口;工作电压为2.0~5.5V,这样可以更好的兼容外部扩展模式的供电特性,无需要再额外的进行电压兼容匹配。

在灵动官网上提供了MM32F0140系列芯片的资料手册(数据手册、用户编程手册、勘误手册、以及丰富的应用笔记)、库函数及示例程序,可以到如下链接进行下载:https://www.mindmotion.com.cn/products/mm32mcu/mm32f/mm32f_value_line/mm32f0140/

硬件环境
基于MM32F0140数据手册中引脚分布图,我们绘制了一块核心板,将芯片所有的功能引脚通过双排针的形式,都引出来了,方便扩展其它功能模块进行连线。

硬件环境:原理图
Schematic_LQFP48_2022-03-19.png

硬件环境:PCB
PCB顶层.png PCB底层.png

硬件环境:实物图
PCBA.png

软件环境
统一使用KEIL MDK集成开发环境,具体的下载、安装过程我们在这边就不一一详述了,可以参考应用笔记的《AN0001 MDK5.18安装指南(中文版)》。如果之前没有开使用过MM32芯片,在第一次进行MM32开发之前,我们还需要安装一下KEIL MDK环境下的MM32芯片支持包,同样可以到官网上下载到芯片支持包和对应的《AN0012 MM32 Series KEIL Pack的安装(中文版)》进行指导操作。

调试工具
我们选用创芯工坊的PWLINK2这个调试下载器,支持市面上多家MCU的调试烧录;另外PWLINK2的接口同时支持5V3.3V这两个供电电压,这样方便了后面扩展模块的供电需求;另外还带有1TLL串口,方便我们程序的调试。
PWLINK2-2.png PWLINK2-1.png

基础工程
基于官方提供的函数库程序,我们使用KEIL MDK建立一个全新的工程作为基础工程;在这个基础工程中,我们使用到了SysTick作为系统嘀嗒时钟,进行任务轮询调度和精确延时功能的实现、使用到了UART1功能,重载并实现了printf函数的调用,并且基于UART1移植了Letter-shell_2.x开源软件,方便在后面程序中的调试、最后就是实现自己编写的TASK任务创建及调度函数,这个在后面的每一应用功能中都会使用到,所以在提供的函数上都有具体的函数说明及注释,所以看明白这个,才会懂程序是如何运行的;这个只是一个简单的基于时间片轮转的调度实现,但很可靠,已经应用在多个实际项目当中了,有兴趣的小伙伴可以详细看一下。

基础工程:SysTick部分
/*******************************************************************************
* SysTick初始化配置
*******************************************************************************/
void SysTick_Init(void)
{
    RCC_ClocksTypeDef  RCC_Clocks;
    RCC_GetClocksFreq(&RCC_Clocks);

    if(SysTick_Config(RCC_Clocks.HCLK_Frequency / 1000) != 0)
    {
        while(1);
    }

    NVIC_SetPriority(SysTick_IRQn, 0);
}


/*******************************************************************************
* SysTick中断函数
*******************************************************************************/
void SysTick_Handler(void)
{
    SysTick_Tick++;
    TASK_TimeSlice(SysTick_Tick);
}


/*******************************************************************************
* SysTick精确毫秒延时函数
*******************************************************************************/
void SysTick_DelayMS(uint32_t Tick)
{
    uint32_t Start = SysTick_Tick;

    if((UINT32_MAX - Start) >= Tick)
    {
        while((SysTick_Tick - Start) != Tick);
    }
    else
    {
        while((SysTick_Tick + (UINT32_MAX - Start)) != Tick);
    }
}

基础工程:UART1 & Letter-shell部分
/*******************************************************************************
* SHELL底层打印函数
*******************************************************************************/
void shellPortWrite(const char ch)
{
    UART_SendData(UART1, (uint8_t)ch);
    while(UART_GetFlagStatus(UART1, UART_IT_TXIEN) == RESET);
}


/*******************************************************************************
* UART1初始化及SHELL
*******************************************************************************/
void shellPortInit(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    UART_InitTypeDef UART_InitStructure;

    RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA,   ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2ENR_UART1, ENABLE);

    NVIC_InitStructure.NVIC_IRQChannel = UART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    UART_StructInit(&UART_InitStructure);
    UART_InitStructure.UART_BaudRate            = 115200;
    UART_InitStructure.UART_WordLength          = UART_WordLength_8b;
    UART_InitStructure.UART_StopBits            = UART_StopBits_1;
    UART_InitStructure.UART_Parity              = UART_Parity_No;
    UART_InitStructure.UART_HardwareFlowControl = UART_HardwareFlowControl_None;
    UART_InitStructure.UART_Mode                = UART_Mode_Rx | UART_Mode_Tx;
    UART_Init(UART1, &UART_InitStructure);

    UART_ITConfig(UART1, UART_IT_RXIEN, ENABLE);

    UART_Cmd(UART1, ENABLE);

    GPIO_PinAFConfig(GPIOA, GPIO_PinSource9,  GPIO_AF_1);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_1);

    GPIO_StructInit(&GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    shell.write = shellPortWrite;
    shellInit(&shell);
}


/*******************************************************************************
* UART1中断函数
*******************************************************************************/
void UART1_IRQHandler(void)
{
    if(UART_GetITStatus(UART1, UART_IT_RXIEN) != RESET)
    {
        shellHandler(&shell, UART_ReceiveData(UART1));
        UART_ClearITPendingBit(UART1,  UART_IT_RXIEN);
    }
}


/*******************************************************************************
* printf打印函数
*******************************************************************************/
int fputc(int ch, FILE *f)
{
    UART_SendData(UART1, (uint8_t)ch);
    while(UART_GetFlagStatus(UART1, UART_IT_TXIEN) == RESET);

    return ch;
}

基础工程:TASK任务调度部分
/*******************************************************************************
* 添加节点
*******************************************************************************/
void LinkedList_AppendNode(TASK_InfoTypeDef info)
{
    LinkedList_TypeDef *node = head;

    /* 如果当前链表为空 */
    if(head == NULL)
    {
        /* 申请一个内存空间 */
        head = (LinkedList_TypeDef *)malloc(sizeof(LinkedList_TypeDef));

        if(head == NULL)
        {
            free(head);         /* 申请不成功, 对其释放 */
        }
        else
        {
            /* 将新申请到的内容空间作为头节点, 对数据信息进行赋值 */
            memcpy(&head->info, &info, sizeof(TASK_InfoTypeDef));

            head->next = NULL;  /* 设置指向的下一个节点为空 */
        }
    }
    else
    {
        /* 找到最后一个节点(最后一个节点的下一个指向为空) */
        while(node->next != NULL)
        {
            node = node->next;
        }

        /* 给最后一个节点的下一个指向申请一个内存空间 */
        node->next = (LinkedList_TypeDef *)malloc(sizeof(LinkedList_TypeDef));

        /* 如果申请成功 */
        if(node->next != NULL)
        {
            node = node->next;  /* 切换到最后一个节点位置 */

            /* 对新节点的数据信息进行赋值 */
            memcpy(&node->info, &info, sizeof(TASK_InfoTypeDef));

            node->next = NULL;  /* 设置指向的下一个节点为空 */
        }
        else
        {
            free(node->next);   /* 申请不成功, 对其释放 */
        }
    }
}


/*******************************************************************************
* 查找节点
*******************************************************************************/
uint8_t LinkedList_SearchNode(uint8_t index)
{
    LinkedList_TypeDef *node = head;

    /* 如果节点不为空 */
    while(node != NULL)
    {
        /* 比较当前节点的下标号值, 节点下标号数值唯一性 */
        if(node->info.index == index)
        {
            return 1;       /* 存在下标号相同的节点 */
        }

        node = node->next;  /* 指向下一个节点 */
    }

    return 0;               /* 不存在下标号相同的节点 */
}


/*******************************************************************************
* 添加任务
*******************************************************************************/
void TASK_Append(uint8_t index, Task_Handler handler, uint32_t tick)
{
    /* 定义一个任务信息结构 */
    TASK_InfoTypeDef info;

    /* 根据参数对其进行赋值 */
    info.index   = index;
    info.ready   = 0;
    info.tick    = tick;
    info.handler = handler;

    /* 判断当前链表中有没有重复的节点*/
    if(LinkedList_SearchNode(index) == 0)
    {
        /* 没有重复的节点, 则添加 */
        LinkedList_AppendNode(info);
    }
    else
    {
        /* 存在重复的节点, 打印提示信息 */
        printf("\r\nDuplicate Task Index!!!");
    }
}


/*******************************************************************************
* 任务时间片处理
*******************************************************************************/
void TASK_TimeSlice(uint32_t tick)
{
    LinkedList_TypeDef *node = head;

    /* 如果节点不为空 */
    while(node != NULL)
    {
        /* 根据当前的TICK与节点配置的TICK进行比较, 如果相余为0, 置位节点的READY标志 */
        if((tick % node->info.tick) == 0)
        {
            node->info.ready = 1;
        }
        
        node = node->next;  /* 指向下一个节点 */
    }
}


/*******************************************************************************
* 任务调度
*******************************************************************************/
void TASK_Scheduling(void)
{
    LinkedList_TypeDef *node = head;

    /* 如果节点不为空, 存在需要被调度的任务 */
    while(node != NULL)
    {
        /* 如果节点当前处于READY状态 */
        if(node->info.ready)
        {
            node->info.ready = 0;
            node->info.handler();   /* 调用节点的处理函数 */
        }

        node = node->next;          /* 指向下一个节点 */
    }
}

基础工程:实际运行效果
Run.png

在附件中提供了PCBGerber产生文件,可以直接使用这个去制作PCB,跟着一起来熟悉MM32及其应用,这个PCB板支持LQFP48封装的MM32所有芯片哦,真正做到了全兼容!

附件
MM32F0140数据手册: DS_MM32F0140_SC.pdf (2.75 MB)

使用特权

评论回复

打赏榜单

21小跑堂 打赏了 100.00 元 2022-03-23
理由:恭喜通过原创奖文章审核!请多多加油哦!

james03| | 2022-3-21 13:48 | 显示全部楼层
感谢楼主的无私奉献

使用特权

评论回复
Q80351951| | 2022-3-24 23:02 | 显示全部楼层
约20年前我做过几十块这样的PCB,记得好像是为MSP430转接的,搞不好现在还在地下室里面呢。

使用特权

评论回复
carpsnow| | 2022-3-30 15:03 | 显示全部楼层
有地下室真好~~~

使用特权

评论回复
tpgf| | 2022-4-3 15:04 | 显示全部楼层
楼主展示的是最小系统板是吗

使用特权

评论回复
aoyi| | 2022-4-3 15:10 | 显示全部楼层
我也非常羡慕啊  真是不错

使用特权

评论回复
zljiu| | 2022-4-3 15:20 | 显示全部楼层
这样方便新手上手哈

使用特权

评论回复
nawu| | 2022-4-3 15:27 | 显示全部楼层
非常不错 刚开始还是调简单的好上手点

使用特权

评论回复
gwsan| | 2022-4-3 15:35 | 显示全部楼层
能不能使用iar啊

使用特权

评论回复
tfqi| | 2022-4-3 15:43 | 显示全部楼层
非常期待楼主的后续哈

使用特权

评论回复
xld0932|  楼主 | 2022-4-4 09:56 | 显示全部楼层
tpgf 发表于 2022-4-3 15:04
楼主展示的是最小系统板是吗

是的,兼容MM32官网上所有带有LPFQ48封装的芯片

使用特权

评论回复
发新帖 本帖赏金 100.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则