1 如何使用keil5建立一个工程文件
本篇文章所讲内容基于LPC1759芯片展开(公司用LPC比较多,M0、M3都用,对于LPC系列芯片,开发流程大同小异,如果以后用到STM32再总结跟大伙分享),比较完整地展现一个软件项目的开发,内容比较基础,希望对一些刚入门的同学有些帮助。(PS:我也是菜鸟,从事应用层的开发已经两年多,但是最近才着手负责一个较完整的小项目,期间的一些小感悟、小收获非常乐意拿出来跟大家交流分享,不对的地方还请各位高手赐教哈~)
打开keil5软件(需要软件安装包的同学私我哈,keil5安装包,以及AK100仿真器驱动都可以提供),创建一个新工程文件,如图1所示。
图1
取文件名称:lpc1759example,如图2所示。
图2
进入选择MCU界面,如图3所示。
图3
通过Manage Run-Time Environment可把keil5工具提供的针对开发者所选的MCU的软件功能加载到文件中,我一般就按照图4所示选择。
图4
说几点,本文创建的工程文件为前后台模式,不采用任何实时操作系统,如果你对keil5工具自带的RTX感兴趣,可以进一步研究,目前我们公司开发用的都是UCOS。另外不太建议直接从Manage Run-Time Environment中加载比如GPIO、UART等等硬件相关的代码,一般针对某款MCU开发时,大多数情况下都可以从网上获得相关的开发例程,如各个硬件初始化、简单应用代码等。所以,建议采用开发例程的代码。
如图5所示,一个初步的工程文件搭建好了,接下来的工作主要就是配置工程文件、加载自己编写的代码丰富工程文件功能。
图5
如图6所示,在"Target1"位置右击,点击“Manage Project Items”,进入图7所示界面,可以增加、删除“Project Targets”"Groups",或者修改名称。还有重要一步,就是配置目标选项。
图6
图7
点击该按钮,进入配置目标选项。
图8、图9跟所选芯片相关,图8就是选择的MCU,图9的ROM、RAM大小自动跟图8所选芯片匹配,采用默认即可。
图8
图9
图10注意勾选“Create Hex File”,否则编译完程序在output里找不到hex文件,图11、图12、图13、图14、图15采用默认配置即可。
图10
图11
图12
图13
图14
图15
图16需要注意的是,你所用的仿真器选择,在这之前一定要安装好仿真器的驱动,否则在图16里找不到你要配置的仿真器,目前我都是用AK100仿真器,对应的就是“TKScope Debug for ARM”,然后点击右边的“Settings”按钮。如图17所示,进行“硬件选择”配置,如图18所示,进行“程序烧写”配置,其他选项可以不用改动,感兴趣的话再细究。
图16
图17
图18
2 开始编写代码
在编写代码前,要根据项目的功能要求,确定使用哪些硬件,根据硬件使用情况,决定主频、外设频率大小。
(这个时候,手头上肯定要准备好芯片用户手册和相关开发例程)。关于主频该取多大,一是考虑芯片最大支持多少,肯定不能超过芯片的要求,二是如果外设使用的不太多也不复杂,建议主频不要太大,太大功耗大,太小对于SPI等读写速率有影响,对于lpc1759,主频定为48MHz(外部晶振频率为12MHz)或主频定为44236800Hz(外部晶振频率为11059200Hz),统一外设频率定为二分之一的主频,至于SPI的CLK到底多大,可以通过SPI相关寄存器分频设置。主频、外设频率设定等系统初始化(一般main函数的首行代码),在“system_LPC17xx.c”文件里修改,内容比较少也比较简单,遇到一些寄存器的设置,可以查用户手册。
如图19所示,新建一个文件,按照图20所示保存。
图19
图20
把main.c文件加载到工程里的UserCode下,如图21所示。接下来开始编写main函数框架,一个简单的框架如图22所示。之后的工作就是根据项目要求,一点一点添加功能,应用层或底层驱动都会涉及到。
图21
图22
3 使用一个硬件,要编写哪些代码?
这一小节,通过UART的例子,跟大家详解一下。牵扯到硬件,对于菜鸟而言,有例程会大大降低开发难度,也容易避免掉进不知名的坑里。用到硬件,首先要初始化,参考例程加上查询芯片用户手册,根据自己的应用要求,写好应该不难。
void UART_Ini(uint8_t PortNum, UARTMODE set,uint32_t baudrate)
{
uint16_t ulFdiv;
//参数过滤保护
if((set.datab<5) ||(set.datab>8)) return;
if( (set.stopb==0)||(set.stopb>2) ) return;
if( set.parity>4 ) return;
if(PortNum == 0) //UART0
{
LPC_PINCON->PINSEL0 |= (0x01 << 4)|(0x01 << 6);
LPC_SC->PCONP |= 0x08; //打开串口0功能
LPC_UART0->LCR = 0x80; //允许设置波特率
ulFdiv = (Fpclk /16) / baudrate; //设置波特率 Fpclk为外设频率
LPC_UART0->DLM = ulFdiv / 256;
LPC_UART0->DLL = ulFdiv % 256;
LPC_UART0->LCR = 0x00; //锁定波特率
LPC_UART0->FCR = 0x87; //使能FIFO 设置8个字节触发点
LPC_UART0->LCR |= (set.datab-5); //数据位设置
if(set.stopb ==2 ) //2位停止位
{
LPC_UART0->LCR |= 0x04;
}
if(set.parity !=0 ) //有校验位
{
LPC_UART0->LCR |= 0x08;
LPC_UART0->LCR |=((set.parity-1)<<4); //奇偶校验设置
}
NVIC_EnableIRQ(UART0_IRQn);
NVIC_SetPriority(UART0_IRQn, 4);
LPC_UART0->IER = 0x01; //使能接收中断
}
}
在硬件初始化函数里调用情况如图23所示。
图23
一般使用UART收发数据,都是采用中断方式,所以还要写相应的中断服务函数。在上面创建的工程文件中,UART0的中断服务函数名有默认的,为UART0_IRQHandler,在Starup_LPC17xx.s中可以找到,采用默认函数名称即可。中断函数如下:
void UART0_IRQHandler (void)
{
uint8_t i;
Start:
if((LPC_UART0->IIR & 0x0F ) == 0x02 ) //发送中断
{
if (GETBIT(ui_FlgUART[UART0],6)) //在发送状态中
{
for (i=0; i<8; i++)
{
if(uc_UartPtrTx[UART0] >= uc_UartTxLen[UART0]) //发送完数据
{
CLRBIT(ui_FlgUART[UART0],6);
uc_UartPtrTx[UART0] = 0;
uc_UartPtrRx[UART0] = 0;
LPC_UART0->IER = 0x01; //使能接收中断
goto End;
}
LPC_UART0->THR = uc_UartTxbuf[UART0][uc_UartPtrTx[UART0]];
uc_UartPtrTx[UART0] ++ ;
}
goto Start;
}
else
{
LPC_UART0->IER = 0x01; //使能接收中断
}
}
if((LPC_UART0->IIR & 0x0F ) == 0x04 ||(LPC_UART0->IIR & 0x0F ) == 0x0C) //接收数据
{
if(!GETBIT(ui_FlgUART[UART0],6)) //不在发送状态
{
s_Tmr10ms.ui_UartRxTimeOut[UART0] = 5;
for(i=0; i<8; i++)
{
if( (LPC_UART0->LSR & 0x01)==0 ) //FIFO空
{
uint8_t uc_Buf;
uc_Buf = LPC_UART0->RBR;
break;
}
uc_UartRxbuf[UART0][uc_UartPtrRx[UART0]] = LPC_UART0->RBR; //接收数据
uc_UartPtrRx[UART0] ++ ;
uc_UartRxLen[UART0] = uc_UartPtrRx[UART0];
}
goto Start;
}
else
{
LPC_UART0->IER = 0x02; //使能发送中断
}
}
End:;
}
在接收数据中断里,通过 s_Tmr10ms.ui_UartRxTimeOut字节超时定时器,来判断数据收没收全,时间到了,数据接收标志置起(在10ms定时器里做)。对于一些有固定帧头帧尾格式的数据报文,在接收里可做详细判断,不必采用字节超时方法。在main函数的主循环中,接收数据处理代码如图24所示。ui_FlgUART[UARTNum]的bit1位即为接收数据标志。
图24
接收完数据做了相关处理后,要发送回复数据,代码构成如图25所示。
图25
void SetRTS(eUARTNum UARTNum)
{
ui_FlgUART[UARTNum] |= 0x0020; //setbit 5
uc_UartPtrTx[UARTNum] = 0;
}
如果是通过硬件RTS控制数据发送的话,SetRTS里还要增加相关的硬件控制,我这里就是置起发送标志,另外清除了一下发送数据长度。
void StartTD485(eUARTNum UARTNum) //发送启动函数
{
uc_UartPtrTx[UARTNum] = 0;
memset(uc_UartRxbuf[UARTNum],0,sizeof(uc_UartRxbuf[UARTNum]));
SETBIT(ui_FlgUART[UARTNum],6);
CLRBIT(ui_FlgUART[UARTNum],5);
if(UARTNum==UART0)
{
LPC_UART0->THR = uc_UartTxbuf[UART0][uc_UartPtrTx[UART0]];
uc_UartPtrTx[UART0] ++ ;
if(uc_UartPtrTx[UART0] >= uc_UartTxLen[UART0]) //发送完数据
{
CLRBIT(ui_FlgUART[UART0],6);
uc_UartPtrTx[UART0] = 0;
uc_UartPtrRx[UART0] = 0;
LPC_UART0->IER = 0x01; //使能接收中断
return;
}
if( uc_UartPtrTx[UART0]!=0)
{
LPC_UART0->IER = 0x02; //使能发送中断还有未发送完的数据进入发送中断接着发送
}
}
}
使用UART收发数据,基本就涉及到以上方面。
最后附一张,我做的一个小项目的工程文件结构图26。建议大家,应用层代码、不同硬件的底层代码放在不同的文件目录里,方面维护,增加或删除文件都比较方便,养成好习惯哦~
图26
代码功能完成好了,接下来就是结合硬件仿真调试了,期间可能会出现各种各样的问题,多跟硬件工程师沟通,很重要哦~