[MCU] 【国民技术低功耗系列N32L43x测评】02.创建模版工程

xld0932   2022-6-26 11:41 楼主

本文章将通过KEIL MDK集成开发环境一步步创建基于N32L43XRL-STB开发板的模版工程,通过原理图设计、实现板载资源LED、KEY、USART的基础功能,并通过USART移植我常用的Letter-shell,下面将详述其过程,文末有调试问题记录,供参考。

 

创建工程

STEP1.打开KEIL MDK集成开发环境软件

1.png

 

STEP2.点击菜单栏Project->New uVision Project...

2.png

 

STEP3.在弹出的Create New Project窗口中输入工程文件名,选择工程文件需要保存的路径后,点击保存按键

3.png

 

STEP4.在弹出的Select Device fro Target窗口中选择开发板上对应的芯片型号:N32L436RB,然后点击OK

4.png

 

STEP5.在弹出的Manage Run-Time Environment窗口中暂时不需要操作,直接点击OK即可

5.png

 

STEP6.此时我们一个基于N32L436RB芯片的空的KEIL工程就创建完成了,接下来我们需要来添加工程文件和配置工程

6.png

 

STEP7.点击工具栏上的Manage Project Items按键,我们来添加工程文件

7.png

 

STEP8.在弹出的Manage Project Items窗口中的Project Items中,我们可以修改Project Targets名称,在Groups中添加文件分组,在Files中添加对应分组中的源代码,操作完成后,我们点击OK

8.png

 

STEP9.添加好后的工程分组和文件信息如下图所示

9.png

 

STEP10.接下来我们继续点击工具栏上的Options for Target按钮,来对工程进行配置

10.png

 

STEP11.在Target中将Code Generation区域的ARM Compiler选择Use default compiler version5、勾选Use MicroLIB

11.png

 

STEP12.在Output中勾选Create HEX File,这样在我们编译完工程后就可以自动生成HEX烧录文件了

12.png

 

STEP13.在C/C++中,将Preprocessor Symbols中的Define填写N32L43X和USE_STDPERIPH_DRIVER这两个宏,这是作用于芯片底层驱动库程序的、接着勾选C99 Mode,这是个人编译习惯,根据需要来选择、然后在Include Paths栏添加头文件的包含路径

13.png 14.png

 

STEP14.在Debug中选择调试下载器:CMSIS-DAP Debugger,然后点击Settings按键,这时我们会看到SW Device区域中已经检测到了MCU(需要将开发板通过USB连接到电脑上)

15.png 16.png

 

STEP15.在Utilities中保持默认选项设置,然后点击Settings按键,在弹出窗口的Flash Download中勾选Reset and Run,这样我们在通过KEIL下载完程序后,会自动复位芯片然后运行程序,在确认了Programming Algorithm中程序下载算法是对应的之后,我们点击OK

17.png 18.png

 

STEP16.此时我们完整的KEIL工程就创建、配置完成了,接下来我们需要编写代码,然后编译、下载程序、运行调试了

 

编写驱动

STEP1.开发板板载了3个彩色LED灯,分别由PA8、PB4和PB5这三个GPIO端口引脚来控制,通过原理图,我们可以看出当GPIO端口引脚输出高电平时,对应的LED灯处于点亮的状态,而当GPIO端口引脚输出底电平时,对应的LED灯处于熄灭的状态;驱动程序如下所示:

24.png

/*******************************************************************************
 * @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按键的识别电平正好是相反的,所以在编程的时候需要注意一下哈;驱动程序如下所示:

25.png 26.png

/*******************************************************************************
 * @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这个开源代码,驱动程序如下所示:

27.png

/*******************************************************************************
 * @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按钮编译工程源代码,确认无误

19.png

 

STEP2.点击工具栏的Download按钮或者按下F8快捷按键,将程序烧到到芯片

20.png

 

STEP3.打开PC端的串口调试软件MobaXterm进行监测,在代码开始运行后,我们看到开发板上的LED灯开始闪烁,串口软件有消息打印输出,同时我们每按下一个按键,在串口终端软件上都会看到对应的打印消息,如下所示:

21.png

 

调试问题

在开发板拿到手后,J5接口中的RESET是没有跳帽短接的,就算在KEIL中配置了Reset and Run这个功能,也仅仅是通过软件复位的方式来复位芯片的,并没有实际去操作芯片的RESET过程,所以导致在J5 RESET没有连接的情况下,芯片上电后USART打印不正常,如下图所示;曾一度以为是NS-LINK或者是芯片复位上电时序的问题呢,担独立按下板载的RESET按键,程序又是正常的……所以在用示波器连接到RESET查看波形后,竟然没有复位电平时序,所以才定位到了J5的RESET跳帽上来……在将J5的RESET跳帽连接上后,程序就运行正常啦!!!

正常上电打印:

23.png

异常上电打印:

22.png

 

附件:

软件工程源代码:

Project.zip (476.74 KB)
(下载次数: 6, 2022-6-26 11:37 上传)

 

运行效果:


 

 

本帖最后由 xld0932 于 2022-6-26 12:14 编辑
We are a team and we work as a team !

回复评论 (3)

帖子写得很好,特别是按键的检测,辛苦了,期待更加优秀的作品!

本帖最后由 lugl4313820 于 2022-6-26 16:57 编辑
点赞  2022-6-26 12:48
引用: lugl4313820 发表于 2022-6-26 12:48 帖子写得很好,特别是按键的检测不,辛苦了,期待更加优秀的作品!

We are a team and we work as a team !
点赞  2022-6-26 12:52

坛友们都是用keil吗?有和我用IAR的吗?

在爱好的道路上不断前进,在生活的迷雾中播撒光引
点赞  2022-7-7 21:41
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复