根据测试要求:
编写上位机软件,支持usb或者网络转can转232。支持LCD显示配置参数以及通信数据,按键进行配置can工具。
1、初步计划:
上位机先采用串口,通过串口配置参数,下发接收LPC1768上报数据。
实现usb转can转232功能。LPC1768实现CDC功能,接收上位机下发数据,上报接收的can和232数据。
LCD显示参数,可以直接按键设置参数。
2、进阶计划
上位机支持网络接口,通过网络转can转232数据。
3、终极计划:
- 实现一个多功能调试工具辅助开发LPC产品。
-USB转CAN
- 移植嵌入式操作系统,如ucos,FreeRTOS或最近比较火的RT-Thread等
完成第一步初步计划测试及成果如下。
1 基本总结
根据计划的要求,我们因该采用串口,USBCDC串口和LCD几个方式完成LPC1768自身串口和2个CAN口数据交换和展示功能。
此过程我的测试包括如下几个部分:
1)完成数据监控所制定的协议
2)LPC1768上的程序及代码
3)PC机上位软件
目前完成的内容中不包括USBCDC方式对数据端口的监控功能,原因是USB接口初始化始终无法成功,而且看了以下本论坛的其他人遇到了相同问题,因此,初步测试计划中不包括USBCDC方式,仅采用LPC1768-DEV串口0作为监控端口,完成测试功能。
LCD菜单部分未完成参数输入,只完成了参数和状态显示。原因是我的上位软件可以管理所有监控端口,不需要LCD的输入。
2 初步测试成果展示2.1 协议制定
协议分为上行和下行两条,以主从问答式为基础。
下行(PC->DEV)
帧格式 | |
| |
| 0x00——无效 0x01——UART0 0x02——UART1 0x03——CAN0 0x04——CAN1 |
| 0x00——无效 0x01——设置通讯对象参数同时打开 0x02——通讯对象控制 0x10——发送数据 0x11——读取数据 |
| |
| 以数据对象所通讯的数据帧为单位,例如:串口以1个字节为单位。 |
上行(DEV->PC)
帧格式 | |
| |
| 0x01——UART0 0x02——UART1 0x03——CAN0 0x04——CAN1 |
| |
| 以数据对象所通讯的数据帧为单位,例如:串口以1个字节为单位。 |
使用方法举例:
1) 串口1参数:波特率=115200bps,数据位=8位,停止位=1位,奇偶校验=无
0xFF,0xFF,0x02,0x01,0x07,0x00,0x01,0xC2,0x00,0x03,0x00,0x00
2) 打开串口1
0xFF, 0xFF,0x02, 0x02,0x01,0x01
3) 关闭串口1
0xFF, 0xFF, 0x02,0x02,0x01,0x00
2.2 DEV程序开发说明
DEV程序包括主监控串口UART0的驱动,串口UART1的驱动,CAN驱动,键盘和显示I2C驱动,以及菜单设计。
l 主程序框架
主程序负责系统初始化,解析PC和IRD-LPC1768-DEV之间的数据,并执行。
l UART驱动
1) 引脚初始化
- static void UART1_Init_Pin(void)
- {
- // Pin configuration for UART0
- PINSEL_CFG_Type PinCfg;
- /*
- * Initialize UART0 pin connect
- */
- PinCfg.Funcnum = 1;
- PinCfg.OpenDrain = 0;
- PinCfg.Pinmode = 0;
- PinCfg.Pinnum = 15;
- PinCfg.Portnum = 0;
- PINSEL_ConfigPin(&PinCfg);
- PinCfg.Pinnum = 16;
- PINSEL_ConfigPin(&PinCfg);
- }
复制代码通过底板原理图和核心板原理图的对比,我们可以确定UART1的引脚为P0.15和P0.16。
2) 串口初始化
- /*********************************************************************//**
- * @brief UART1 initinal
- * @param[in] uint32_t baud
- * UART_DATABIT_Type db data bits
- * UART_STOPBIT_Type sb stop bits
- * UART_PARITY_Type pt parity
- * @return None
- **********************************************************************/
- void UART1_Init(void)
- {
- // UART FIFO configuration Struct variable
- UART_FIFO_CFG_Type UARTFIFOConfigStruct;
- uint32_t idx, len;
- __IO FlagStatus exitflag;
- uint8_t buffer[10];
- /* Initialize UART Configuration parameter structure to default state:
- * Baudrate = 9600bps
- * 8 data bit
- * 1 Stop bit
- * None parity
- */
- // UART_ConfigStructInit(&UARTConfigStruct);
- // Initialize UART0 peripheral with given to corresponding parameter
- UART_Init((LPC_UART_TypeDef *)LPC_UART1, &UARTConfigStruct);
- /* Initialize FIFOConfigStruct to default state:
- * - FIFO_DMAMode = DISABLE
- * - FIFO_Level = UART_FIFO_TRGLEV0
- * - FIFO_ResetRxBuf = ENABLE
- * - FIFO_ResetTxBuf = ENABLE
- * - FIFO_State = ENABLE
- */
- UART_FIFOConfigStructInit(&UARTFIFOConfigStruct);
- // Initialize FIFO for UART0 peripheral
- UART_FIFOConfig((LPC_UART_TypeDef *)LPC_UART1, &UARTFIFOConfigStruct);
- // Disable THRE interrupt
- UART_IntConfig((LPC_UART_TypeDef *)LPC_UART1, UART_INTCFG_THRE, DISABLE);
- // Enable UART Transmit
- UART_TxCmd((LPC_UART_TypeDef *)LPC_UART1, ENABLE);
- /* Enable UART Rx interrupt */
- UART_IntConfig((LPC_UART_TypeDef *)LPC_UART1, UART_INTCFG_RBR, ENABLE);
- /* Enable UART line status interrupt */
- UART_IntConfig((LPC_UART_TypeDef *)LPC_UART1, UART_INTCFG_RLS, ENABLE);
- // Reset ring buf head and tail idx
- __BUF_RESET(rb.rx_head);
- __BUF_RESET(rb.rx_tail);
- __BUF_RESET(rb.tx_head);
- __BUF_RESET(rb.tx_tail);
- /* preemption = 1, sub-priority = 1 */
- NVIC_SetPriority(UART1_IRQn, ((0x01<<3)|0x02));
- /* Enable Interrupt for UART0 channel */
- NVIC_EnableIRQ(UART1_IRQn);
- DeviceOpen = 1;
- }
复制代码采用查询发送,中断接收的方式,接收和发送都采用了消息队列的方式作为二级缓存管理。
3) 参数设置接口
- //参数设置接口
- void UART1_SetParam(uint8_t *dat)
- {
- memcpy(&UARTConfigStruct, dat, sizeof(UART_CFG_Type));
- UART1_Init_Pin();
- UART1_Init();
- }
- 通过上位软件设置串口参数,并使能。
复制代码4) 状态查询
返回UART1的当前状态,可以在LCD上看到状态的变化。
5) 中断+消息队列管理
- /*----------------- INTERRUPT SERVICE ROUTINES --------------------------*/
- /*********************************************************************//**
- * @brief UART1 interrupt handler sub-routine
- * @param[in] None
- * @return None
- **********************************************************************/
- void UART1_IRQHandler(void)
- {
- uint32_t intsrc, tmp, tmp1;
- /* Determine the interrupt source */
- intsrc = UART_GetIntId((LPC_UART_TypeDef*)LPC_UART1);
- tmp = intsrc & UART_IIR_INTID_MASK;
- // Receive Line Status
- if (tmp == UART_IIR_INTID_RLS){
- // Check line status
- tmp1 = UART_GetLineStatus((LPC_UART_TypeDef*)LPC_UART1);
- // Mask out the Receive Ready and Transmit Holding empty status
- tmp1 &= (UART_LSR_OE | UART_LSR_PE | UART_LSR_FE \
- | UART_LSR_BI | UART_LSR_RXFE);
- // If any error exist
- if (tmp1) {
- // UART_IntErr(tmp1);
- }
- }
- // Receive Data Available or Character time-out
- if ((tmp == UART_IIR_INTID_RDA) || (tmp == UART_IIR_INTID_CTI)){
- if (!__BUF_IS_FULL(rb.rx_head,rb.rx_tail)){
- rb.rx[rb.rx_head] = UART_ReceiveByte((LPC_UART_TypeDef*)LPC_UART1);
- __BUF_INCR(rb.rx_head);
- }
- }
- }
- 采用中断+消息队列方式,可以保证串口数据不丢失。
复制代码l CAN驱动
DEV上有两个CAN接口,CAN的驱动针对CAN1和CAN2是相同的,这里以CAN1接口为例。
1) CAN引脚初始化
- static void CAN1_Init_Pin(void)
- {
- PINSEL_CFG_Type PinCfg;
- /* Pin configuration
- * CAN1: select P0.0 as RD1. P0.1 as TD1
- * CAN2: select P2.7 as RD2, P2.8 as RD2
- */
- PinCfg.Funcnum = 1;
- PinCfg.OpenDrain = 0;
- PinCfg.Pinmode = 0;
- PinCfg.Pinnum = 0;
- PinCfg.Portnum = 0;
- PINSEL_ConfigPin(&PinCfg);
- PinCfg.Pinnum = 1;
- PINSEL_ConfigPin(&PinCfg);
- }
复制代码通过底板原理图和核心板原理图的对比,我们可以确定CAN1的引脚为P0.0和P0.1,CAN2的引脚为P0.4和P0.5。
2) CAN设备初始化
- void CAN2_Init(uint32_t baud)
- {
- CAN2_Init_Pin();
- //Initialize CAN1 & CAN2
- CAN_Init(LPC_CAN2, baud);
- //Enable Interrupt
- CAN_IRQCmd(LPC_CAN2, CANINT_RIE, ENABLE);
- CAN_SetAFMode(LPC_CANAF,CAN_AccBP);
- // Reset ring buf head and tail idx
- __BUF_RESET(can2.rx_head);
- __BUF_RESET(can2.rx_tail);
- //Enable CAN Interrupt
- NVIC_EnableIRQ(CAN_IRQn);
-
- devicecan2 = 1;
- }
复制代码3) CAN状态查询
返回CAN的当前状态,可以在LCD上看到状态的变化。
4) CAN接收
同样采用中断+消息队列的方式。
- /*----------------- INTERRUPT SERVICE ROUTINES --------------------------*/
- /*********************************************************************//**
- * @brief CAN_IRQ Handler, control receive message operation
- * param[in] none
- * @return none
- **********************************************************************/
- void CAN_IRQHandler()
- {
- uint8_t IntStatus;
- uint32_t data,i;
- CAN_MSG_Type RXMsg; // messages for test Bypass mode
-
- /* get interrupt status
- * Note that: Interrupt register CANICR will be reset after read.
- * So function "CAN_IntGetStatus" should be call only one time
- */
- if( devicecan1)
- {
- IntStatus = CAN_IntGetStatus(LPC_CAN1);
- //check receive interrupt
- if((IntStatus>>0)&0x01)
- {
- CAN_ReceiveMsg(LPC_CAN1,&RXMsg);
- if (!__BUF_IS_FULL(can1.rx_head,can1.rx_tail)){
- memcpy((void *)&can1.rx[can1.rx_head],(void *)&RXMsg,sizeof(CAN_MSG_Type));
- __BUF_INCR(can1.rx_head);
- }
- }
- }
- if( devicecan2 )
- {
- IntStatus = CAN_IntGetStatus(LPC_CAN2);
- //check receive interrupt
- if((IntStatus>>0)&0x01)
- {
- CAN_ReceiveMsg(LPC_CAN2,&RXMsg);
- if (!__BUF_IS_FULL(can2.rx_head,can2.rx_tail)){
- memcpy((void *)&can2.rx[can2.rx_head],(void *)&RXMsg,sizeof(CAN_MSG_Type));
- __BUF_INCR(can2.rx_head);
- }
- }
- }
- }
- /*********************************************************************//**
- * @brief can1 read function for interrupt mode (using ring buffers)
- * @param[in] UARTPort Selected UART peripheral used to send data,
- * should be UART0
- * @param[out] rxbuf Pointer to Received buffer
- * @param[in] buflen Length of Received buffer
- * @return Number of bytes actually read from the ring buffer
- **********************************************************************/
- uint32_t CAN1Receive(CAN_MSG_Type *rxbuf)
- {
- CAN_MSG_Type *data = (CAN_MSG_Type *) rxbuf;
- uint32_t bytes = 0;
- /* Loop until receive buffer ring is empty or
- until max_bytes expires */
- if(!(__BUF_IS_EMPTY(can1.rx_head, can1.rx_tail)))
- {
- /* Read data from ring buffer into user buffer */
- memcpy(data, (void *)&can1.rx[can1.rx_tail], sizeof(CAN_MSG_Type));
- /* Update tail pointer */
- __BUF_INCR(can1.rx_tail);
- /* Increment data count and decrement buffer size count */
- bytes++;
- }
- return bytes;
- }
复制代码中断中接收CAN消息,并且填入消息队列中,通过CAN1Receive()函数每次读取一个CAN消息,直到读完为止。
5) CAN发送
CAN的发送采用查询式,接到上位的数据后,调用发送函数,每次发送1帧数据。
- void CAN1_Send(uint8_t *data)
- {
- CAN_MSG_Type msg;
- memcpy((void *)&msg.id,data,4);
- if(msg.id >> 11)
- msg.format = EXT_ID_FORMAT;
- else
- msg.format = STD_ID_FORMAT;
- msg.len = 8;
- msg.type = DATA_FRAME;
- memcpy((void *)msg.dataA,data+4,4);
- memcpy((void *)msg.dataB,data+8,4);
- if(devicecan1)
- CAN_SendMsg(LPC_CAN1, &msg);
- }
复制代码l 键盘及LED驱动
键盘和LED驱动主要指I2C驱动。
1) I2C初始化
系统中保留I2C0作为所有I2C外设的统一接口,并用PCA9551芯片完成板载4个按键和4个LED的采集和驱动。
通过底板原理图和核心板原理图的对比,我们可以确定I2C0的引脚为P0.27和P0.28。
2) 键盘
通过这个函数,可以获取PCA9551上连接的4个板载按键输入,分别占用返回值的低4位,每一位代表一个按键输入,1表示按键输入有效,0表示按键输入无效。
- char GetPCA9551_Btn(void)
- {
- uint8_t pca9551_buf[10];
- uint8_t btn;
- pca9551_buf[0]=0x00;
- rxsetup.sl_addr7bit = PCA9551_SLVADDR;
- rxsetup.tx_data = pca9551_buf; // Get address to read at writing address
- rxsetup.tx_length = 1;
- rxsetup.rx_data = &btn;
- rxsetup.rx_length = 1;
- rxsetup.retransmissions_max = 3;
- I2C_MasterTransferData(I2CDEV, &rxsetup, I2C_TRANSFER_POLLING);
- btn = ~btn;
- btn &= 0x0f;
- return btn;
- }
复制代码3) LED驱动
LED驱动通过PCA9551_SetLed(uint8_tled)函数点亮板载4个LED,通过void PCA9551_ClrLed(uint8_t led)函数熄灭4个LED,LED的选择通过传入参数的第4位表示,为1表示操作有效,为0表示操作无效。
- void WritePCA9551(void)
- {
- uint8_t pca9551_buf[10];
- txsetup.sl_addr7bit = PCA9551_SLVADDR;
- txsetup.tx_data = pca9551_buf;
- txsetup.tx_length = 3;
- txsetup.rx_data = NULL;
- txsetup.rx_length = 0;
- txsetup.retransmissions_max = 3;
-
- pca9551_buf[0]=0x15;
- pca9551_buf[1]=0x55;
- pca9551_buf[2]=Led_Status;
- I2C_MasterTransferData(I2CDEV, &txsetup, I2C_TRANSFER_POLLING);
-
- }
-
- void PCA9551_SetLed(uint8_t led)
- {
- if(led & 0x01)
- Led_Status &= ~(0x03);
- if(led & 0x02)
- Led_Status &= ~(0x0c);
- if(led & 0x04)
- Led_Status &= ~(0x30);
- if(led & 0x08)
- Led_Status &= ~(0xc0);
-
- WritePCA9551();
- }
-
- void PCA9551_ClrLed(uint8_t led)
- {
- if(led & 0x01)
- Led_Status |= 0x01;
- if(led & 0x02)
- Led_Status |= 0x04;
- if(led & 0x04)
- Led_Status |= 0x10;
- if(led & 0x08)
- Led_Status |= 0x40;
- WritePCA9551();
- }
复制代码l LCD驱动和菜单
LCD驱动借鉴了官方提供的程序,调了几天才最终调通。
1)LCD初始化
- static void LCD_Init_Pin(void)
- {
- PINSEL_CFG_Type PinCfg;
- /* Pin configuration
- * gpio: select P2.0 - P2.7 displaybus
- * gpio: select P2.8 RS,P2.10 RD,P2.9 WR
- */
- PinCfg.Funcnum = 0;
- PinCfg.OpenDrain = 0;
- PinCfg.Pinmode = 0;
- PinCfg.Pinnum = 0;
- PinCfg.Portnum = 2;
- PINSEL_ConfigPin(&PinCfg);
- PinCfg.Pinnum = 1;
- PINSEL_ConfigPin(&PinCfg);
- PinCfg.Pinnum = 2;
- PINSEL_ConfigPin(&PinCfg);
- PinCfg.Pinnum = 3;
- PINSEL_ConfigPin(&PinCfg);
- PinCfg.Pinnum = 4;
- PINSEL_ConfigPin(&PinCfg);
- PinCfg.Pinnum = 5;
- PINSEL_ConfigPin(&PinCfg);
- PinCfg.Pinnum = 6;
- PINSEL_ConfigPin(&PinCfg);
- PinCfg.Pinnum = 7;
- PINSEL_ConfigPin(&PinCfg);
- PinCfg.Pinnum = 8;
- PINSEL_ConfigPin(&PinCfg);
- PinCfg.Pinnum = 10;
- PINSEL_ConfigPin(&PinCfg);
- PinCfg.Pinnum = 22;
- PinCfg.Portnum = 0;
- PINSEL_ConfigPin(&PinCfg);
-
- GPIO_SetDir(2, 0x00000FF | LCD_RS_BIT | LCD_E_BIT, 1);
- GPIO_SetDir(0, LCD_RW_BIT, 1);
- }
- /*---------------------------------------------------------------------------*
- * Routine: LCD_ActivateLCD
- *---------------------------------------------------------------------------*
- * Description:
- * Turn on the char display and initialize.
- * Inputs:
- * void
- *---------------------------------------------------------------------------*/
- void LCD_ActivateLCD(void)
- {
- LCD_Init_Pin();
- // Ensure enable is LOW
- LCDE_Ctl(1);
- // Put into instruction mode
- // Wait at least 15 ms (we'll do 25 ms)
- LCDRS_Ctl(0);
- delay_ms(25);
- LCD_WriteDataOrInstr(0x02);
- delay_us(100);
- // Two lines, 1/16 duty cycle, 5x8 dots Operation Mode
- // Needs 39 uS, use 100 uS
- LCD_WriteDataOrInstr(
- INSTR_FUNC(IFUNC_8BIT|IFUNC_TWO_LINE|IFUNC_DISPLAY_ON));
- delay_us(100);
- // Display On: display on, cursor off, blinking off
- // Needs 39 uS, use 100 uS
- LCD_WriteDataOrInstr(
- INSTR_DISPLAY(IDISPLAY_ON|IDISPLAY_CURSOR_OFF|IDISPLAY_BLINK_OFF));
- delay_us(100);
- LCD_WriteDataOrInstr(
- INSTR_SET_DDRAM_ADDR(0));
- delay_us(100);
-
- // Clear the display
- // Needs 1.53 ms, use 5 ms
- LCD_WriteDataOrInstr( INSTR_CLEAR);
- delay_ms(5);
- // Now go into data entry mode with auto increment and auto shift off
- // Needs 39 uS, use 100 uS
- LCD_WriteDataOrInstr(
- INSTR_ENTRY_MODE(IENTRY_INCREMENT|IENTRY_SHIFT_OFF));
- delay_us(100);
- iRow = 0;
- iColumn = 0;
- }
复制代码通过底板原理图和核心板原理图的对比,我们可以确定LCD的引脚为P2.0到P2.9和P2.10,P0.22。
2)显示一个字符串
该函数可以在指定的行列开始,显示一行英文字符和数字。
- void asc_tran(uint8_t row, uint8_t col, char *asc)
- {
- LCD_SetCursor(col,row);
- while((*asc) != 0)
- {
- LCD_WriteData(*asc);
- asc++;
- }
- }
复制代码3)显示信息的菜单
- static void DispRefresh(void)
- {
- int i;
- LCD_ClearScreen();
- for(i=0;i<4;i++)
- asc_tran(i,0,dispmenu[i]);
-
- }
- void Display_M1(void)
- {
- int i;
- char btn;
- if(dispflag == 0)
- {
- strcpy(dispmenu[0],title);
- for(i=0;i<3;i++)
- strcpy(dispmenu[1+i],mainmenu[curptr+i]);
- DispRefresh();
- dispflag = 1;
- }
- btn = GetPCA9551_Btn();
- switch(btn)
- {
- case BTN_UP:
- if(curptr > 0)
- curptr--;
- dispflag = 0;
- break;
- case BTN_DW:
- if(curptr < 1)
- curptr++;
- dispflag = 0;
- break;
- default:
- break;
- }
- }
复制代码
下一贴将展示上位机软件和实际的测试结果.