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

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

[复制链接]
8985|12
 楼主| 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部分
  1. /*******************************************************************************
  2. * SysTick初始化配置
  3. *******************************************************************************/
  4. void SysTick_Init(void)
  5. {
  6.     RCC_ClocksTypeDef  RCC_Clocks;
  7.     RCC_GetClocksFreq(&RCC_Clocks);

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

  12.     NVIC_SetPriority(SysTick_IRQn, 0);
  13. }


  14. /*******************************************************************************
  15. * SysTick中断函数
  16. *******************************************************************************/
  17. void SysTick_Handler(void)
  18. {
  19.     SysTick_Tick++;
  20.     TASK_TimeSlice(SysTick_Tick);
  21. }


  22. /*******************************************************************************
  23. * SysTick精确毫秒延时函数
  24. *******************************************************************************/
  25. void SysTick_DelayMS(uint32_t Tick)
  26. {
  27.     uint32_t Start = SysTick_Tick;

  28.     if((UINT32_MAX - Start) >= Tick)
  29.     {
  30.         while((SysTick_Tick - Start) != Tick);
  31.     }
  32.     else
  33.     {
  34.         while((SysTick_Tick + (UINT32_MAX - Start)) != Tick);
  35.     }
  36. }

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


  9. /*******************************************************************************
  10. * UART1初始化及SHELL
  11. *******************************************************************************/
  12. void shellPortInit(void)
  13. {
  14.     GPIO_InitTypeDef GPIO_InitStructure;
  15.     NVIC_InitTypeDef NVIC_InitStructure;
  16.     UART_InitTypeDef UART_InitStructure;

  17.     RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA,   ENABLE);
  18.     RCC_APB2PeriphClockCmd(RCC_APB2ENR_UART1, ENABLE);

  19.     NVIC_InitStructure.NVIC_IRQChannel = UART1_IRQn;
  20.     NVIC_InitStructure.NVIC_IRQChannelPriority = 1;
  21.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  22.     NVIC_Init(&NVIC_InitStructure);

  23.     UART_StructInit(&UART_InitStructure);
  24.     UART_InitStructure.UART_BaudRate            = 115200;
  25.     UART_InitStructure.UART_WordLength          = UART_WordLength_8b;
  26.     UART_InitStructure.UART_StopBits            = UART_StopBits_1;
  27.     UART_InitStructure.UART_Parity              = UART_Parity_No;
  28.     UART_InitStructure.UART_HardwareFlowControl = UART_HardwareFlowControl_None;
  29.     UART_InitStructure.UART_Mode                = UART_Mode_Rx | UART_Mode_Tx;
  30.     UART_Init(UART1, &UART_InitStructure);

  31.     UART_ITConfig(UART1, UART_IT_RXIEN, ENABLE);

  32.     UART_Cmd(UART1, ENABLE);

  33.     GPIO_PinAFConfig(GPIOA, GPIO_PinSource9,  GPIO_AF_1);
  34.     GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_1);

  35.     GPIO_StructInit(&GPIO_InitStructure);
  36.     GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_9;
  37.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  38.     GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;
  39.     GPIO_Init(GPIOA, &GPIO_InitStructure);

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

  43.     shell.write = shellPortWrite;
  44.     shellInit(&shell);
  45. }


  46. /*******************************************************************************
  47. * UART1中断函数
  48. *******************************************************************************/
  49. void UART1_IRQHandler(void)
  50. {
  51.     if(UART_GetITStatus(UART1, UART_IT_RXIEN) != RESET)
  52.     {
  53.         shellHandler(&shell, UART_ReceiveData(UART1));
  54.         UART_ClearITPendingBit(UART1,  UART_IT_RXIEN);
  55.     }
  56. }


  57. /*******************************************************************************
  58. * printf打印函数
  59. *******************************************************************************/
  60. int fputc(int ch, FILE *f)
  61. {
  62.     UART_SendData(UART1, (uint8_t)ch);
  63.     while(UART_GetFlagStatus(UART1, UART_IT_TXIEN) == RESET);

  64.     return ch;
  65. }

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

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

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

  20.             head->next = NULL;  /* 设置指向的下一个节点为空 */
  21.         }
  22.     }
  23.     else
  24.     {
  25.         /* 找到最后一个节点(最后一个节点的下一个指向为空) */
  26.         while(node->next != NULL)
  27.         {
  28.             node = node->next;
  29.         }

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

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

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

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


  46. /*******************************************************************************
  47. * 查找节点
  48. *******************************************************************************/
  49. uint8_t LinkedList_SearchNode(uint8_t index)
  50. {
  51.     LinkedList_TypeDef *node = head;

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

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

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


  64. /*******************************************************************************
  65. * 添加任务
  66. *******************************************************************************/
  67. void TASK_Append(uint8_t index, Task_Handler handler, uint32_t tick)
  68. {
  69.     /* 定义一个任务信息结构 */
  70.     TASK_InfoTypeDef info;

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

  76.     /* 判断当前链表中有没有重复的节点*/
  77.     if(LinkedList_SearchNode(index) == 0)
  78.     {
  79.         /* 没有重复的节点, 则添加 */
  80.         LinkedList_AppendNode(info);
  81.     }
  82.     else
  83.     {
  84.         /* 存在重复的节点, 打印提示信息 */
  85.         printf("\r\nDuplicate Task Index!!!");
  86.     }
  87. }


  88. /*******************************************************************************
  89. * 任务时间片处理
  90. *******************************************************************************/
  91. void TASK_TimeSlice(uint32_t tick)
  92. {
  93.     LinkedList_TypeDef *node = head;

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


  106. /*******************************************************************************
  107. * 任务调度
  108. *******************************************************************************/
  109. void TASK_Scheduling(void)
  110. {
  111.     LinkedList_TypeDef *node = head;

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

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

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

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

附件
MM32F0140数据手册: DS_MM32F0140_SC.pdf (2.75 MB, 下载次数: 4)
MM32F0140用户手册:附件太大,到官网下载吧
MM32F0140勘误手册: ES_MM32F0140_SC.pdf (204.1 KB, 下载次数: 4)
MM32F0140芯片支持包: MindMotion.MM32F0140_DFP.0.0.6.pack.zip (29.09 KB, 下载次数: 7)
MM32F0140硬件原理图: Schematic_LQFP48_2022-03-19.pdf (126.38 KB, 下载次数: 9)
MM32F0140硬件Gerber文件: Gerber_PCB_LQFP48.zip (99.92 KB, 下载次数: 7)
MM32F0140库函数及示例程序: MM32F0140_Lib_Samples.zip (3.38 MB, 下载次数: 11)
文中提及到的应用笔记: AN0001_MDK5_18_install_SC.pdf (337.74 KB, 下载次数: 8) AN0012_MM32_Series_Keil_pack_install_SC.pdf (745.44 KB, 下载次数: 6)

工程源代码: Template.zip (709.53 KB, 下载次数: 23)

打赏榜单

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封装的芯片
tianle 发表于 2024-10-21 14:20 | 显示全部楼层
suncat0504 发表于 2024-10-23 19:36 | 显示全部楼层
很细致啊。谢谢!能做成通用的最小系统板,可以适配不同型号的,方便!
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:King.Xu

77

主题

3023

帖子

38

粉丝
快速回复 在线客服 返回列表 返回顶部