本帖最后由 GrandLine 于 2022-6-12 10:16 编辑
#申请原创# @21小跑堂
有幸入围了凌鸥创芯电机开发板LKS32M081免费开发板评测活动,收到开发板就迫不及待的开始想玩转起来了……我也是头一回接触凌鸥创芯的MCU。这回申请的开发板主控是LKS08系列的芯片,它是针对电机驱动市场的高性能电机运动控制系列芯片;使用Cortex-M0作为MCU内核,最高工作频率可达到96MHz;集成了仪表级全差分可编程增益放大器和差分ADC,在不需要电压偏置的情况下就可以处理正负信号;集成了12位精度的同步双采样3Msps高速ADC;集成了全温范围可达到千分之五以内的电压基准源;同时还集成了凌鸥自主DSP指令集,可作为异构独立核心运行程序,三角函数、开方运算都可以在100ns内完成运算,给电机的算法控制提供了强有力的支撑。
LKS08系列的芯片工作温度范围在-40℃到+105℃,内部自带的RC时钟源在全温范围内可以达到±1%以内,此外芯片引脚还通过了6KV的接触式放电测试和15KV的非接触式空气静电放电测试,这些给芯片的稳定、可靠运行提供了保障。
LKS08系列的芯片带有丰富的模拟运放和比较器资源,可满足单电阻、双电阻、三电阻电流采样拓扑架构的不同需求;内部集成高压钳位网络,允许高压共模信号直接输入,可以轻松的实现MOSFET内阴直接电流采样的功能,节省了昂贵的电流采样电阻并降低了系统功耗。这样集成度高的MCU,尽最大可能的节省了BOM成本,使客户产品更具有竞争优势。
下面切入正题,本文将介绍基于LKS08系列MCU从搭建开发环境、创建新工程、编写工程代码这几步来讲解,在实操过程中将遇到的一些问题和注意事项,给大家分享一下: 1.准备工作 2.安装芯片PACK支持包 3.创建基础工程 4.编写工程代码:LED、KEY、基于UART0移植的Letter-shell 5.下载运行演示
准备工作 目前官网上仅提供了基于KEIL4和KEIL5的Device Pack,所以我们开发IDE环境是基于KEIL MDK软件,另外就是我常用的MobaXterm调试终端软件。
LKS_EVB_MCU081_V2.0核心板和LKD_EVB_MVPOWPRE_V4.0电机驱动底板各1块,J-LINK调试下载工具、USB转TTL工具、因为LKS_EVB_MCU081_V2.0核心板上板载设计的是SWD下载接口,所以还带了一个自制的JTAG转SWD接口转接小板。
凌鸥的开发资料都可以通过官网下载到,相对于入门开发的小伙伴来说,资料提供得还是比较全的,我也整理了一下,如下所示: ……
安装芯片PACK支持包 我使用的中KEIL5集成开发环境,所以在官方上下载了相对应的Device Pack文件(https://www.linkosemi.com/uploadfiles/Updatefile/nutstore/Keil_v5_device_pack/Linko.LKS08x_v1.0.5.rar),在解压后直接双击Linko.LKS08x_v1.0.5.pack进行安装,如下图所示;在安装完成后,我们就可以在芯片列表中找到凌鸥的芯片型号,创建工程进行开发了:
创建基础工程 STEP1.打开KEIL5 MDK软件,点击菜单栏Project->New uVision Project...
STEP2.在弹出的Create New Project窗口中选择工程存放的路径以及命名项目文件名:
STEP3.在弹出的Select Device for Target窗口中选择凌鸥芯片型号:LKS32MC081C8T8:
STEP4.在弹出的Manage Run-Time Environment窗口中不作任何操作,直接点击OK:
STEP5.此时一个基于LKS32MC081C8T8芯片的空项目工程就创建出来了,接下来我们还需要向工程中添加分组、添加程序以及配置参数等操作:
STEP6.点击工具栏上的Manage Project Items按钮:
STEP7.在弹出的Manage Project Items窗口中的Project Items选项卡中,将Project Targets重新命名为LKS32MC081C8T8、在Groups中添加分组、然后在对应的Group中再添加Files源程序:
STEP8.完整的工程目录文件如下所示:
需要注意的是我们一般源程序都是*.c/*.h类型的文件,但在添加官方库程序时有一个.o类型的文件lks32mc08x_nvr.o,这是KEIL编译后输出的文件,可以理解为看不到源码形式的程序文件,也可以直接编译;如果不添加这个文件会有Read_Trim的错误提示哦!!!
STEP9.点击工具栏上的Options for Target...按钮:
STEP10.在弹出的Options for Target窗口的Target选项卡中,将Code Generation选择使用Use default compiler version5,勾选Use MicroLIB:
STEP11.在Output选项卡中,可以设置Name of Executable,如果需要生成HEX烧录文件则需要勾选Create HEX File选项,将Browse Information勾选是为了程序实现过程中或者是调试过程中的便捷:
STEP12.在C/C++选项卡中,对包含的头文件路径Include Paths进行配置:
STEP13.在Debug选项卡中,选择相就的调试工具,并点击Settings进行后一级设置,如果提示未知的芯片型号,我们默认选择Cortex-M0即可,然后将程序烧录接口方式修改为SW模式,这样在SW Device中就可以成功检测到芯片了:
STEP14.在Utilities选项卡中,选择Use Target Driver for Flash Programming选项,默认勾选Use Debug Driver和Update Target before Debugging,然后点击Settings进行后一级设置,确认Programming Algorithm下载算法的选择是正确的,并勾选Reset and Run,这样在程序下载完成后,就可以自动复位运行了:
STEP15.到此整个项目工程就完全创建、配置完成了,接下来就可以开始编写程序代码了。
编写工程代码 打开官方的示例程序,我们使用到了LKS32MC08x_Periph_Driver中的库程序;示例中官方除了startup_lks32mc08x.s启动文件之外,并没有看到我们常见的system_lks32mc08x.c文件;通过查阅官方示例工程源程序,我们看到多出了一个hardware_init.c文件,在这个文件中实现了对MCU使用外设的初始化和startup文件中需要调用的SystemInit函数;但个人习惯,还是把这个hardware_init.c的文件名给改写成了system_lks32mc08x.c,只实现了SystemInit对系统时钟的配置,其余的外设初始化放到了其它的.c文件中;另外还有一个需要注意的文件是hardware_config.h文件,这个在底层库程序中都有包含,它主要是包含了底层库程序的一些头文件,以及共用的宏定义,所以这个文件一定要包含进来哈!!!最后一个就是interrupt.c文件,官方是将所有的中断函数实现集中在一个文件中的,这样写各有优缺点,我还是按照之前我的写法,在使用到的时候再具体实现,因为本身在startup_lks32mc08x.s文件中加了weak的修饰,我不用的时候完全不需要去写个空函数嘛。
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
/* LED1 <-> P0.6 */
GPIO_StructInit(&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIO0, &GPIO_InitStruct);
GPIO_WriteBit(GPIO0, GPIO_Pin_6, Bit_RESET);
/* LED2 <-> P0.7 */
GPIO_StructInit(&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIO0, &GPIO_InitStruct);
GPIO_WriteBit(GPIO0, GPIO_Pin_7, Bit_RESET);
/* LED3 <-> P0.3 */
GPIO_StructInit(&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIO0, &GPIO_InitStruct);
GPIO_WriteBit(GPIO0, GPIO_Pin_3, Bit_SET);
TASK_Append(TASK_ID_LED, LED_Toggle, 250);
}
void LED_Toggle(void)
{
if(!GPIO_ReadOutputDataBit(GPIO0, GPIO_Pin_3))
{
GPIO_WriteBit(GPIO0, GPIO_Pin_3, Bit_SET);
}
else
{
GPIO_WriteBit(GPIO0, GPIO_Pin_3, Bit_RESET);
}
}
void KEY_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
/* START <-> P2.11 */
GPIO_StructInit(&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIO2, &GPIO_InitStruct);
/* STOP <-> P2.12 */
GPIO_StructInit(&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIO2, &GPIO_InitStruct);
TASK_Append(TASK_ID_KEY, KEY_Scan, 10);
}
void KEY_SubScan(uint8_t *State, uint8_t *Count, uint8_t Value, char *Name)
{
if(*State == 0)
{
if(Value == Bit_RESET) *Count += 1;
else *Count = 0;
if(*Count > 5)
{
*Count = 0; *State = 1;
printf("\r\n%s Pressed", Name);
}
}
else
{
if(Value != Bit_RESET) *Count += 1;
else *Count = 0;
if(*Count > 5)
{
*Count = 0; *State = 0;
printf("\r\n%s Release", Name);
}
}
}
void KEY_Scan(void)
{
static uint8_t KeyState[2] = {0, 0};
static uint8_t KeyCount[2] = {0, 0};
KEY_SubScan(&KeyState[0], &KeyCount[0], GPIO_ReadInputDataBit(GPIO2, GPIO_Pin_11), "START");
KEY_SubScan(&KeyState[1], &KeyCount[1], GPIO_ReadInputDataBit(GPIO2, GPIO_Pin_12), "STOP ");
}
void shellPortWrite(const char ch)
{
UART0_BUFF = (uint8_t)ch;
while(!(UART0_IF & UART_IF_SendOver));
UART0_IF = UART_IF_SendOver;
}
void shellPortInit(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
UART_InitTypeDef UART_InitStruct;
/* UART0_RXD <-> P0.15 */
GPIO_StructInit(&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_15;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIO0, &GPIO_InitStruct);
/* UART0_TXD <-> P1.0 */
GPIO_StructInit(&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIO1, &GPIO_InitStruct);
GPIO_PinAFConfig(GPIO0, GPIO_PinSource_15, AF4_UART);
GPIO_PinAFConfig(GPIO1, GPIO_PinSource_0, AF4_UART);
UART_StructInit(&UART_InitStruct);
UART_InitStruct.BaudRate = 115200;
UART_InitStruct.WordLength = UART_WORDLENGTH_8b;
UART_InitStruct.StopBits = UART_STOPBITS_1b;
UART_InitStruct.FirstSend = UART_FIRSTSEND_LSB;
UART_InitStruct.ParityMode = UART_Parity_NO;
UART_InitStruct.IRQEna = UART_IRQEna_RcvOver;
UART_Init(UART0, &UART_InitStruct);
UART0_IF = 0xFF;
NVIC_SetPriority(UART0_IRQn, 3);
NVIC_EnableIRQ(UART0_IRQn);
shell.write = shellPortWrite;
shellInit(&shell);
}
void UART0_IRQHandler(void)
{
if(UART0_IF & UART_IF_RcvOver)
{
UART0_IF = UART_IF_RcvOver;
shellHandler(&shell, UART0_BUFF);
}
}
int fputc(int ch, FILE *f)
{
UART0_BUFF = (uint8_t)ch;
while(!(UART0_IF & UART_IF_SendOver));
UART0_IF = UART_IF_SendOver;
return ch;
}
下载运行演示 - 在工程源代码中还添加了对SysTick的初始化,然后结合TASK任务进行时间片轮转调度;完成所有程序编写之后,我们就可以编译工程源代码啦:
- 编译无误后,我们通过工具栏上的Download按钮将程序下载到芯片中:
- 通过MobaXterm软件我们可以看到芯片启动之后的运行打印信息、板载的LED处于翻转显示的状态、按下抬起START和STOP按键也有对应的状态信息打印出来:
软件工程源代码
后续 对于MCU来说,我可以驾轻就熟的去编写代码,实现功能;但对于电机应用控制来说我还是个新手;后续通过边学边做的形式,来熟悉凌鸥MCU的外设资源,结合电机的实际调试来做一些分享。
|