本文章将通过KEIL MDK集成开发环境一步步创建基于N32L43XRL-STB开发板的模版工程,通过原理图设计、实现板载资源LED、KEY、USART的基础功能,并通过USART移植我常用的Letter-shell,下面将详述其过程,文末有调试问题记录,供参考。
创建工程
STEP1.打开KEIL MDK集成开发环境软件
STEP2.点击菜单栏Project->New uVision Project...
STEP3.在弹出的Create New Project窗口中输入工程文件名,选择工程文件需要保存的路径后,点击保存按键
STEP4.在弹出的Select Device fro Target窗口中选择开发板上对应的芯片型号:N32L436RB,然后点击OK
STEP5.在弹出的Manage Run-Time Environment窗口中暂时不需要操作,直接点击OK即可
STEP6.此时我们一个基于N32L436RB芯片的空的KEIL工程就创建完成了,接下来我们需要来添加工程文件和配置工程
STEP7.点击工具栏上的Manage Project Items按键,我们来添加工程文件
STEP8.在弹出的Manage Project Items窗口中的Project Items中,我们可以修改Project Targets名称,在Groups中添加文件分组,在Files中添加对应分组中的源代码,操作完成后,我们点击OK
STEP9.添加好后的工程分组和文件信息如下图所示
STEP10.接下来我们继续点击工具栏上的Options for Target按钮,来对工程进行配置
STEP11.在Target中将Code Generation区域的ARM Compiler选择Use default compiler version5、勾选Use MicroLIB
STEP12.在Output中勾选Create HEX File,这样在我们编译完工程后就可以自动生成HEX烧录文件了
STEP13.在C/C++中,将Preprocessor Symbols中的Define填写N32L43X和USE_STDPERIPH_DRIVER这两个宏,这是作用于芯片底层驱动库程序的、接着勾选C99 Mode,这是个人编译习惯,根据需要来选择、然后在Include Paths栏添加头文件的包含路径
STEP14.在Debug中选择调试下载器:CMSIS-DAP Debugger,然后点击Settings按键,这时我们会看到SW Device区域中已经检测到了MCU(需要将开发板通过USB连接到电脑上)
STEP15.在Utilities中保持默认选项设置,然后点击Settings按键,在弹出窗口的Flash Download中勾选Reset and Run,这样我们在通过KEIL下载完程序后,会自动复位芯片然后运行程序,在确认了Programming Algorithm中程序下载算法是对应的之后,我们点击OK
STEP16.此时我们完整的KEIL工程就创建、配置完成了,接下来我们需要编写代码,然后编译、下载程序、运行调试了
编写驱动
STEP1.开发板板载了3个彩色LED灯,分别由PA8、PB4和PB5这三个GPIO端口引脚来控制,通过原理图,我们可以看出当GPIO端口引脚输出高电平时,对应的LED灯处于点亮的状态,而当GPIO端口引脚输出底电平时,对应的LED灯处于熄灭的状态;驱动程序如下所示:
/*******************************************************************************
* @brief * @param
* @retval
* @attention *******************************************************************************/
void LED_Init(void)
{
GPIO_InitType GPIO_InitStructure;
RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOA, ENABLE);
GPIO_InitStruct(&GPIO_InitStructure);
GPIO_InitStructure.Pin = GPIO_PIN_8;
GPIO_InitStructure.GPIO_Current = GPIO_DC_4mA;
GPIO_InitStructure.GPIO_Pull = GPIO_No_Pull;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitPeripheral(GPIOA, &GPIO_InitStructure);
GPIO_WriteBit(GPIOA, GPIO_PIN_8, Bit_RESET);
RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOB, ENABLE);
GPIO_InitStruct(&GPIO_InitStructure);
GPIO_InitStructure.Pin = GPIO_PIN_4;
GPIO_InitStructure.GPIO_Current = GPIO_DC_4mA;
GPIO_InitStructure.GPIO_Pull = GPIO_No_Pull;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitPeripheral(GPIOB, &GPIO_InitStructure);
GPIO_WriteBit(GPIOB, GPIO_PIN_4, Bit_RESET);
GPIO_InitStruct(&GPIO_InitStructure);
GPIO_InitStructure.Pin = GPIO_PIN_5;
GPIO_InitStructure.GPIO_Current = GPIO_DC_4mA;
GPIO_InitStructure.GPIO_Pull = GPIO_No_Pull;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitPeripheral(GPIOB, &GPIO_InitStructure);
GPIO_WriteBit(GPIOB, GPIO_PIN_5, Bit_RESET);
TASK_Append(TASK_ID_LED, LED_Toggle, 250);
}
/*******************************************************************************
* @brief
* @param
* @retval
* @attention
*******************************************************************************/
void LED_Toggle(void)
{
#if 0
if(!GPIO_ReadOutputDataBit(GPIOA, GPIO_PIN_8))
{
GPIO_WriteBit(GPIOA, GPIO_PIN_8, Bit_SET);
}
else
{
GPIO_WriteBit(GPIOA, GPIO_PIN_8, Bit_RESET);
}
#else
static uint8_t Index = 0;
switch(Index)
{
case 0:
GPIO_WriteBit(GPIOA, GPIO_PIN_8, Bit_SET);
GPIO_WriteBit(GPIOB, GPIO_PIN_4, Bit_RESET);
GPIO_WriteBit(GPIOB, GPIO_PIN_5, Bit_RESET);
break;
case 1:
GPIO_WriteBit(GPIOA, GPIO_PIN_8, Bit_RESET);
GPIO_WriteBit(GPIOB, GPIO_PIN_4, Bit_SET);
GPIO_WriteBit(GPIOB, GPIO_PIN_5, Bit_RESET);
break;
case 2:
GPIO_WriteBit(GPIOA, GPIO_PIN_8, Bit_RESET);
GPIO_WriteBit(GPIOB, GPIO_PIN_4, Bit_RESET);
GPIO_WriteBit(GPIOB, GPIO_PIN_5, Bit_SET);
break;
default:
break;
}
Index = (Index + 1) % 3;
#endif
}
STEP2.开发板板载了3个用户按键和一个WAKEUP按键,3个用户按键分别由PA4、PA5和PA6这三个GPIO端口引脚来控制,通过原理图,我们可以看出当按键按下后,对应的按键状态是处于与GND连通的状态,处于低电平,所以我们需要将这三个按键的常态(按键不处于被按下的状态)配置为上拉输入,这样我们就可以通过检测按键的电平状态来判断按键是否按下了;而另外一个按键是WAKEUP按键,连接在PA0这个端口引脚上,它可以作为普通按键功能使用,也可以作为MCU在低功耗模式下的唤醒按键;当作为唤醒按键功能时,需要串联一个电阻到地,通过按键连接到MCU_VDD,以上升沿的方式唤醒处于低功耗模式下的MCU;而作为普通按键功能时,我只需要将这个引脚配置成浮空输入即可,因为常态下这个商品引脚已经被一个串联的电阻拉到了GND上,而当按下的时候会被按键短路到MCU_VCC上,由此来判断按键的状态;这边的3个用户按键和WAKEUP按键的识别电平正好是相反的,所以在编程的时候需要注意一下哈;驱动程序如下所示:
/*******************************************************************************
* @brief
* @param
* @retval
* @attention
*******************************************************************************/
void KEY_Init(void)
{
GPIO_InitType GPIO_InitStructure;
RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOA, ENABLE);
GPIO_InitStruct(&GPIO_InitStructure);
GPIO_InitStruct(&GPIO_InitStructure);
GPIO_InitStructure.Pin = GPIO_PIN_0;
GPIO_InitStructure.GPIO_Pull = GPIO_No_Pull;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Input;
GPIO_InitPeripheral(GPIOA, &GPIO_InitStructure);
GPIO_InitStruct(&GPIO_InitStructure);
GPIO_InitStruct(&GPIO_InitStructure);
GPIO_InitStructure.Pin = GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6;
GPIO_InitStructure.GPIO_Pull = GPIO_Pull_Up;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Input;
GPIO_InitPeripheral(GPIOA, &GPIO_InitStructure);
TASK_Append(TASK_ID_KEY, KEY_Scan, 10);
}
/*******************************************************************************
* @brief
* @param
* @retval
* @attention
*******************************************************************************/
void KEY_SubScan(uint8_t *State, uint8_t *Count, uint8_t Value, uint8_t Active, char *Name)
{
if(*State == 0)
{
if(Value == Active) *Count += 1;
else *Count = 0;
if(*Count > 5)
{
*Count = 0; *State = 1;
printf("\r\n%s Pressed", Name);
}
}
else
{
if(Value != Active) *Count += 1;
else *Count = 0;
if(*Count > 5)
{
*Count = 0; *State = 0;
printf("\r\n%s Release", Name);
}
}
}
/*******************************************************************************
* @brief
* @param
* @retval
* @attention
*******************************************************************************/
void KEY_Scan(void)
{
static uint8_t KeyState[4] = {0, 0, 0, 0};
static uint8_t KeyCount[4] = {0, 0, 0, 0};
KEY_SubScan(&KeyState[0], &KeyCount[0], GPIO_ReadInputDataBit(GPIOA, GPIO_PIN_0), Bit_SET, "WAKEUP");
KEY_SubScan(&KeyState[1], &KeyCount[1], GPIO_ReadInputDataBit(GPIOA, GPIO_PIN_4), Bit_RESET, "KEY1 ");
KEY_SubScan(&KeyState[2], &KeyCount[2], GPIO_ReadInputDataBit(GPIOA, GPIO_PIN_5), Bit_RESET, "KEY2 ");
KEY_SubScan(&KeyState[3], &KeyCount[3], GPIO_ReadInputDataBit(GPIOA, GPIO_PIN_6), Bit_RESET, "KEY3 ");
}
STEP3.板载的NS-LINK除了有调试、下载程序的功能,还集成了一个虚拟串口,将它连接到MCU的USART上我们可以打印出一些需要的信息到PC端;通过原理图,我们可以看到通过跳帽的方式可以将MCU的USART1与NS-LINK的TTL串口进行连接;另外基于USART我们还移植了Letter-shell这个开源代码,驱动程序如下所示:
/*******************************************************************************
* @brief
* @param
* @retval
* @attention
*******************************************************************************/
void shellPortWrite(const char ch)
{
USART_SendData(USART1, (uint8_t)ch);
while (USART_GetFlagStatus(USART1, USART_FLAG_TXDE) == RESET);
}
/*******************************************************************************
* @brief
* @param
* @retval
* @attention
*******************************************************************************/
void shellPortInit(void)
{
GPIO_InitType GPIO_InitStructure;
NVIC_InitType NVIC_InitStructure;
USART_InitType USART_InitStructure;
RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOA, ENABLE);
RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_USART1, ENABLE);
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
GPIO_InitStruct(&GPIO_InitStructure);
GPIO_InitStructure.Pin = GPIO_PIN_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Alternate = GPIO_AF4_USART1;
GPIO_InitPeripheral(GPIOA, &GPIO_InitStructure);
GPIO_InitStruct(&GPIO_InitStructure);
GPIO_InitStructure.Pin = GPIO_PIN_10;
GPIO_InitStructure.GPIO_Pull = GPIO_Pull_Up;
GPIO_InitStructure.GPIO_Alternate = GPIO_AF4_USART1;
GPIO_InitPeripheral(GPIOA, &GPIO_InitStructure);
USART_StructInit(&USART_InitStructure);
USART_InitStructure.BaudRate = 115200;
USART_InitStructure.WordLength = USART_WL_8B;
USART_InitStructure.StopBits = USART_STPB_1;
USART_InitStructure.Parity = USART_PE_NO;
USART_InitStructure.HardwareFlowControl = USART_HFCTRL_NONE;
USART_InitStructure.Mode = USART_MODE_RX | USART_MODE_TX;
USART_Init(USART1, &USART_InitStructure);
USART_ClrFlag(USART1, USART_FLAG_RXDNE);
USART_ConfigInt(USART1, USART_INT_RXDNE, ENABLE);
USART_Enable(USART1, ENABLE);
shell.write = shellPortWrite;
shellInit(&shell);
}
/*******************************************************************************
* @brief
* @param
* @retval
* @attention
*******************************************************************************/
void USART1_IRQHandler(void)
{
if(USART_GetIntStatus(USART1, USART_INT_RXDNE) != RESET)
{
shellHandler(&shell, USART_ReceiveData(USART1));
USART_ClrIntPendingBit(USART1, USART_INT_RXDNE);
}
}
/*******************************************************************************
* @brief
* @param
* @retval
* @attention
*******************************************************************************/
int fputc(int ch, FILE *f)
{
USART_SendData(USART1, (uint8_t)ch);
while (USART_GetFlagStatus(USART1, USART_FLAG_TXDE) == RESET);
return ch;
}
编译、下载、调试运行
STEP1.在我们完成驱动程序编写完成后,点击工具栏的Built或者Rebuild按钮编译工程源代码,确认无误
STEP2.点击工具栏的Download按钮或者按下F8快捷按键,将程序烧到到芯片
STEP3.打开PC端的串口调试软件MobaXterm进行监测,在代码开始运行后,我们看到开发板上的LED灯开始闪烁,串口软件有消息打印输出,同时我们每按下一个按键,在串口终端软件上都会看到对应的打印消息,如下所示:
调试问题
在开发板拿到手后,J5接口中的RESET是没有跳帽短接的,就算在KEIL中配置了Reset and Run这个功能,也仅仅是通过软件复位的方式来复位芯片的,并没有实际去操作芯片的RESET过程,所以导致在J5 RESET没有连接的情况下,芯片上电后USART打印不正常,如下图所示;曾一度以为是NS-LINK或者是芯片复位上电时序的问题呢,担独立按下板载的RESET按键,程序又是正常的……所以在用示波器连接到RESET查看波形后,竟然没有复位电平时序,所以才定位到了J5的RESET跳帽上来……在将J5的RESET跳帽连接上后,程序就运行正常啦!!!
正常上电打印:
异常上电打印:
附件:
软件工程源代码:
运行效果:
本帖最后由 xld0932 于 2022-6-26 12:14 编辑
帖子写得很好,特别是按键的检测,辛苦了,期待更加优秀的作品!
本帖最后由 lugl4313820 于 2022-6-26 16:57 编辑