- 前言
这是我第一次参加Funpack的活动,之前各种单片机、DSP、FPGA用的的很多,但是NXP的单片机还没用过,看到这次的开发板是NXP双核M33的单片机,我果断去digi-key买了一块开发板,报名参加了这次活动。
- LPC55S69介绍
LPC55S6x MCU家族是全球首款基于通用Cortex-M33的微控制器。
该高效率MCU家族采用Armv8-M架构,性能和高级安全功能达到新水平,包括TrustZone-M和协处理器扩展。LPC55S6x家族利用协处理器扩展型号,大幅提高信号处理效率,采用专有DSP加速器,使计算的时钟周期减少了10倍。还可选择使用第二个Cortex-M33内核,支持灵活地平衡高性能与功率效率。
此外,LPC55S6x MCU家族依托基于40nm NVM的处理技术,具备成本效益优势,提供广泛的可扩展封装和存储器选项,并提供强大的支持,包括MCUXpresso软件和工具生态系统及低成本开发板。
上图是LPC55S69的 Block Diagram,我们可以看出该芯片内部拥有2个M33的内核,其中一个比较精简,是辅助的CPU。存储方面最大可拥有640KB flash和320 KB RAM。各种外设也很丰富,16Bit ADC 、内置高速USB PHY、8个功能可配置的FlexComm、SDIO、各种定时器,该有的外设一应俱全。
- LPC55S69-EVK
这次使用的板子是NXP官方的开发板,该电路板包括一个高性能板载调试器、音频子系统和加速度传感器,提供多个选项,可为网络、传感器、显示器和其他接口添加现成的附加板。
板卡技术和功能规格
- 任务
实现USB-UART Hub,自己设计通信方式,实现USB到多路UART的收发。
我这次要做的任务是实现一个USB转双串口的功能。USB转UART在嵌入式开发中是必不可少的工具,我一直使用的FT4232转4路UART的板子,由于经常需要使用不止一个串口,使用起来相比CH340那种单路的方便很多。这次我就使用LPC55S69制作一个USB转双串口的调试工具。
- 任务实现
NXP官方提供了很多的DEMO,其中有一个DEMO——dev_composite_cdc_vcom_cdc_vcom_bm,
从介绍中我们可以知道这个DEMO实现了USB转双串口的功能,但是并没有引出外部信号。
下面是这个demo的框图:
在这个demo中,CDC1和CDC2是两个虚拟的串口,但其并没有引出信号,PC经USB将数据发送至CDC后,CDC直接将数据传输回来。
这个demo已经很接近我们的目标了,我们只需要添加两个串口,在USB虚拟的串口收到PC发来的数据后通过串口发送至外界,同时在串口通过中断收到数据后,再将接受到的数据通过USB发送到PC,便实现了USB转串口的功能。下图是本项目的框图:
/*!
* @brief Application task function.
*
* This function runs the task for application.
*
* @return None.
*/
void USB_DeviceCdcVcomTask(void)
{
usb_status_t error = kStatus_USB_Error;
volatile usb_cdc_vcom_struct_t *vcomInstance;
for (uint8_t i = 0; i < USB_DEVICE_CONFIG_CDC_ACM; i++)
{
vcomInstance = &g_deviceComposite->cdcVcom[i];
if ((1 == vcomInstance->attach) && (1 == vcomInstance->startTransactions))
{
/* User Code */
/* endpoint callback length is USB_CANCELLED_TRANSFER_LENGTH (0xFFFFFFFFU) when transfer is canceled */
//判断接受区是否为空
if ((0 != vcomInstance->recvSize) && (USB_CANCELLED_TRANSFER_LENGTH != vcomInstance->recvSize))
{
int32_t i;
/* Copy Buffer to Send Buff */
//将接收区数据拷贝至发送区
for (i = 0; i < vcomInstance->recvSize; i++)
{
vcomInstance->currSendBuf[vcomInstance->sendSize++] = vcomInstance->currRecvBuf[i];
}
vcomInstance->recvSize = 0;
}
//判断发送区是否为空
if (vcomInstance->sendSize)
{
uint32_t size = vcomInstance->sendSize;
vcomInstance->sendSize = 0;
//发送数据
error = USB_DeviceCdcAcmSend(vcomInstance->cdcAcmHandle, vcomInstance->bulkInEndpoint,
vcomInstance->currSendBuf, size);
if (error != kStatus_USB_Success)
{
/* Failure to send Data Handling code here */
}
}
}
}
}
这段程序是官方程序中USB的Task,下图是该程序流程图。这段程序主要功能是检测接受区数据是否为空,非空拷贝至发送区;然后检测发送区是否为空,非空便将数据发送出去。
我们主要修改的程序就是这个Task,下面是我的程序:
void USB_DeviceCdcVcomTask(void)
{
usb_status_t error = kStatus_USB_Error;
volatile usb_cdc_vcom_struct_t *vcomInstance;
for (uint8_t i = 0; i < USB_DEVICE_CONFIG_CDC_ACM; i++)
{
vcomInstance = &g_deviceComposite->cdcVcom[i];
if ((1 == vcomInstance->attach) && (1 == vcomInstance->startTransactions))
{
/* User Code */
/* endpoint callback length is USB_CANCELLED_TRANSFER_LENGTH (0xFFFFFFFFU) when transfer is canceled */
if ((0 != vcomInstance->recvSize) && (USB_CANCELLED_TRANSFER_LENGTH != vcomInstance->recvSize))
{
int32_t i;
/* Copy Buffer to Send Buff */
for (i = 0; i < vcomInstance->recvSize; i++)
{
vcomInstance->currSendBuf[vcomInstance->sendSize++] = vcomInstance->currRecvBuf[i];
}
vcomInstance->recvSize = 0;
}
if (vcomInstance->sendSize)
{
uint32_t size = vcomInstance->sendSize;
vcomInstance->sendSize = 0;
// error = USB_DeviceCdcAcmSend(vcomInstance->cdcAcmHandle, vcomInstance->bulkInEndpoint,
// NULL, size);
// if (error != kStatus_USB_Success)
// {
// /* Failure to send Data Handling code here */
// }
if(i==0)
USART_WriteBlocking(FLEXCOMM0_PERIPHERAL,vcomInstance->currSendBuf,size);
else
USART_WriteBlocking(FLEXCOMM2_PERIPHERAL,vcomInstance->currSendBuf,size);
}
}
}
}
这里源程序是将发送去程序调用USB_DeviceCdcAcmSend()将数据通过USB发送出去,这里我们借鸡下蛋,改用串口发送,便实现了数据通过USB发送至串口的功能。下面是我的程序的流程图,可以看出只是在发送部分不一致。
到这里接收功能已经实现,下面实现发送功能。原程序是接收后直接发送,这里我是能了串口接收中断,直接在串口中断函数中接收1位数据,然受调用USB_DeviceCdcAcmSend()函数将其传送至USB。
下面是两个串口的中断函数:
/* FLEXCOMM0_IRQn interrupt handler */
void FLEXCOMM0_FLEXCOMM_IRQHANDLER(void)
{
/* Place your code here */
uint8_t rec;
volatile usb_cdc_vcom_struct_t *vcom_1;
vcom_1 = &g_deviceComposite->cdcVcom[0];
/* If new data arrived. */
if ((kUSART_RxFifoNotEmptyFlag | kUSART_RxError) & USART_GetStatusFlags(FLEXCOMM0_PERIPHERAL))
{
rec = USART_ReadByte(FLEXCOMM0_PERIPHERAL);
USB_DeviceCdcAcmSend(g_deviceComposite->cdcVcom[0].cdcAcmHandle, vcom_1->bulkInEndpoint, &rec, 1);
}
/* Add for ARM errata 838869, affects Cortex-M4, Cortex-M4F
Store immediate overlapping exception return operation might vector to incorrect interrupt. */
#if defined __CORTEX_M && (__CORTEX_M == 4U)
__DSB();
#endif
}
/* FLEXCOMM2_IRQn interrupt handler */
void FLEXCOMM2_FLEXCOMM_IRQHANDLER(void)
{
/* Place your code here */
uint8_t rec;
volatile usb_cdc_vcom_struct_t *vcom_2;
vcom_2 = &g_deviceComposite->cdcVcom[1];
/* If new data arrived. */
if ((kUSART_RxFifoNotEmptyFlag | kUSART_RxError) & USART_GetStatusFlags(FLEXCOMM2_PERIPHERAL))
{
rec = USART_ReadByte(FLEXCOMM2_PERIPHERAL);
USB_DeviceCdcAcmSend(g_deviceComposite->cdcVcom[1].cdcAcmHandle, vcom_2->bulkInEndpoint, &rec, 1);
}
/* Add for ARM errata 838869, affects Cortex-M4, Cortex-M4F
Store immediate overlapping exception return operation might vector to incorrect interrupt. */
#if defined __CORTEX_M && (__CORTEX_M == 4U)
__DSB();
#endif
}
自此我们便完成了数据从串口到USB的发送功能。
两个Flexcomm的初始化代码我这里就不放了,采用MCUXpresso Config Tools直接生成,配置正确即可。
- 总结感悟
第一次使用NXP的处理器和IDE环境,对其有些不适应,不过研究了一下,还是很快就能上手的。这个小任务还是比较简单的,在demo上进行简单的修改便可实现功能。十分感谢硬禾学堂和digikey,让我有机会同NXP的人员、各位小伙伴进行学习交流,祝硬禾学堂越来越 “硬核” ,Funpack的活动能一直办下去