搜索

tag 标签: UART

相关帖子

版块 作者 回复/查看 最后发表
一种基于FPGA的UART电路设计 attachment FPGA/CPLD zxopenljx 2020-3-26 1 127 兰博 2020-3-26 09:45
MSP430g2553硬件UART(基于官方例程的修改) attach_img 【微控制器 MCU】 fish001 2020-3-12 0 202 fish001 2020-3-12 22:07
【工程源码】NIOS II下基于中断的UART接收和发送设计示例代码 attach_img 【Altera SoC】 小梅哥 2020-2-25 0 303 小梅哥 2020-2-25 18:58
msp430f5529简单uart源程序(用串口助手 发什么 回复什么) 【微控制器 MCU】 Aguilera 2020-2-14 0 303 Aguilera 2020-2-14 20:30
使用 KiCad 设计 SC16IS752 板的一个过程 attach_img PCB设计 arm8686 2019-4-11 7 1195 serialworld 2019-12-27 22:26
【NXP Rapid IoT评测】UART0调试串口-Debug Print Demo attach_img RF/无线 yilonglucky 2019-1-16 2 1036 yilonglucky 2019-1-17 22:56
DSP 5502 的串口UART 使用DMA方式传输数据,一次传输16个字节 【DSP 与 ARM 处理器】 光亮自在 2018-8-14 5 1753 光亮自在111 2018-8-22 16:15
proteus串口仿真问题 attach_img 【51单片机】 tangrui1220 2016-1-22 7 3215 tangrui1220 2016-1-26 13:45
悬赏 求大神解答:UART - [悬赏 10 分芯积分] attachment 【微控制器 MCU】 sherlockikougan 2015-1-10 6 1840 姜辣best 2016-1-3 17:02
关于msp430g2553UART的调试 attachment 【微控制器 MCU】 风的世界 2013-11-11 9 6444 zhao_金辉 2016-5-28 20:23
关于msp430g2553UART的调试【问题求助】 【微控制器 MCU】 风的世界 2013-11-11 1 1477 风的世界 2013-11-11 18:04
单片机UART输出问题 attachment 单片机 第七天魔王 2013-9-24 14 4871 chunyang 2013-9-26 14:46
UART Bootloader for Hercules TMS570LS04x MCU attachment 【微控制器 MCU】 蓝雨夜 2013-9-27 1 1807 清柠 2015-6-9 13:45
CC2541_SensorTag 的CC254x Serial Bootloader (SBL) attachment 【无线连接】 蓝雨夜 2013-9-27 0 3585 蓝雨夜 2013-9-27 08:20
【PSoC4心得】UART通讯 attachment 单片机 abu315 2013-10-22 0 1554 abu315 2013-10-22 11:25
關於捕獲資料與UART 【微控制器 MCU】 w765432001 2013-11-1 4 1441 machinnneee 2013-11-6 13:08
给你写的launchpad430--UART attachment 【微控制器 MCU】 qinkaiabc 2013-11-29 0 1396 qinkaiabc 2013-11-29 19:56
【liklon玩GD32F350】二、串口中断实验 attach_img 【GD32 MCU】 liklon 2018-9-3 0 808 liklon 2018-9-3 17:44
MSP430g2553硬件UART(基于官方例程的修改) attach_img 【微控制器 MCU】 Aguilera 2020-2-10 2 378 li123123 2020-3-9 21:39
uart.write函数如何输出0XFF 0XFF 0XFF三个字节(HEX数据,不是字符串数据) 【MicroPython开源版块】 hupozhao 2020-2-29 1 202 philips_lu 2020-3-1 08:58

相关日志

分享 论STM的HAL库与外设死机
manhuami2007 2017-1-4 22:38
论STM的HAL库与外设死机
前段时间的一个项目比较简单,就使用ST提供的CubeMx直接生成了工程,并在其基础上编写程序。不过串口用着用着就会出现死机而处理器没事。这是为什么? 通过调试和阅读代码,发现是因为串口出现了类似死锁的问题。 什么是死锁? 死锁是指两个或两个以上进程在执行过程中,由于竞争资源或者由于彼此通讯而造成的阻塞状态。 因此死锁的重点是多个进程使用同一资源。 没有操作系统为何出现的多个进程呢? 没有操作系统的嵌入式系统中也存在两个进程: 后台程序,即 main() 等主循环函数 中断处理程序 那么HAL库怎么造成了外设死锁的? 根据上面的分析,我们知道是由于主循环在使用外设的同时,中断也要使用这个外设。 先看看HAL库操作外设的简单流程图 程序先检查外设是否上锁,如果上锁则直接退出,没有上锁则上锁之后接着进行相应的设置最后执行后续操作。 问题就出在这个上锁上。我的程序使用的外设是UART,使用中断接收数据,使用DMA发送数据。在中断中收到一个字节之后,再打开中断接收下一个字节。在主循环中使用DMA发送相关的数据。 那么当主程序中使用正在设置UART发送时,也就是UART还处于锁定的状态时,进入了UART接收中断,这时在中断用又要设置UART接收,此时UART处于锁定状态,所以UART的接收设置肯定会失败。从而造成UART不能再接收数据。 怎么处理呢? 这种情况下,我们不能在中断中等待外设释放锁定,因为此时主循环中的程序无法执行,因此不能释放资源。 所以我们应该在中断中通过函数的返回值判断操作是否成功,若没有成功则设置一个标志位,然后再在主循环中检查这个标志位,并在主循环中重新进行操作。 总结 避免死锁最根本的方法就是防止在主循环与中断中同时使用同一外设。 如果不能避免,那么尽量做到执行每个函数后,都判断返回值是否正确,并对执行失败的操作做相应的处理。 欢迎各位添加我的微信公众号“光豆儿”
374 次阅读|0 个评论
分享 stm32f107vc 的uart4 使用dma方式进行数据收发
hugo0chen 2016-10-9 01:42
stm32f107vc 的uart4  使用dma方式进行数据收发
今天花了半天的时间搞了下stm32f107的uart 4使用dma方式进行数据的收发。dma的好处进过测试后,很是好用啊,为cpu省了很多的开销。 主要来看看如何使用dma吧,通常的几个设置参数部分,包括时钟、gpio、usart、dma、中断优先级、中断函数。 1、uart 4如果使用dma方式进行数据收发,需要打开GPIOC 的clock,以及DMA2的clock(stm32f107的dma有两个,dma1和dma2,此部分uart4_tx和uart4_rx映射到的是dma2的channel 5和dma2的channel 3; 2、gpio配置,将PC10--TX和PC11--RX配置为push-pull outputmode,以及infloating input mode 3、dma2的初始化,注意tx和rx的dma初始化时不同的,包括存储器地址(外设地址相同,都是uart-DR),另外dma的方向也不相同。 4、配置dma2的中断,接收TC中断以及发送中断;(在接收处理的时候可以结合使用uart4的idle中断) 5、设置中断的优先级,注意主优先级和次优先级; 6、不要忘记是能uart4以及dma功能。 (晚些时候补充代码) #include "GlobalVar.h" #include "usart.h" #include "stm32f10x_dma.h" #include "string.h" #define UART4_DMA_BUF_NUM 8 unsigned char receiveBuf ; unsigned char receiveCount; static uint8_t uart4_rxdata ; USART_RECV_CALLBACK _usart_recv_callbacks = { 0 }; #ifdef __GNUC__ /* With GCC/RAISONANCE, small printf (option LD Linker-Libraries-Small printf set to 'Yes') calls __io_putchar() */ #define PUTCHAR_PROTOTYPE int __io_putchar(int ch) #else #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f) #endif /* __GNUC__ */ static void uart4_trx_dma_config(void) { DMA_InitTypeDef DMA_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2,ENABLE); DMA_DeInit(DMA2_Channel3); DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)UART4-DR; DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)uart4_rxdata; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize = UART4_DMA_BUF_NUM; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; DMA_InitStructure.DMA_Priority = DMA_Priority_Low; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA2_Channel3, DMA_InitStructure); DMA_ITConfig( DMA2_Channel3, DMA_IT_TC, ENABLE ); DMA_ITConfig(DMA2_Channel3,DMA_IT_TE,ENABLE); USART_DMACmd(UART4,USART_DMAReq_Rx,ENABLE); DMA_Cmd(DMA2_Channel3, ENABLE); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); NVIC_InitStructure.NVIC_IRQChannel = DMA2_Channel3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(NVIC_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = DMA2_Channel5_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(NVIC_InitStructure); } uint32_t USART_Configuration(const struct USARTDefine* configs, uint16_t len) { uint16_t u = 0; NVIC_InitTypeDef NVIC_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; for( u = 0; u len; ++u) { struct USARTDefine config = configs ; if( config.usartdef == UART5 ) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART5, ENABLE); _usart_recv_callbacks = config.recvcallback; } else if(config.usartdef == UART4 ) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART4, ENABLE); _usart_recv_callbacks = config.recvcallback; } GPIO_InitStructure.GPIO_Pin = config.pin_of_txd; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(config.pin_of_txd_group, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = config.pin_of_rxd; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(config.pin_of_rxd_group, GPIO_InitStructure); USART_InitStructure.USART_BaudRate = config.bandrate; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(config.usartdef, USART_InitStructure); if( config.recvcallback != 0 ) { USART_ITConfig(config.usartdef,USART_IT_RXNE,ENABLE); if( config.usartdef == UART5 ) { NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; } else if(config.usartdef == UART4 ) { NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; } NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(NVIC_InitStructure); } USART_Cmd(config.usartdef, ENABLE); if(config.usartdef == UART4 ){ //USART_ITConfig(UART4,USART_IT_IDLE,ENABLE); NVIC_InitStructure.NVIC_IRQChannel = UART4_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(NVIC_InitStructure); USART_ClearFlag(UART4,USART_FLAG_IDLE); USART_ClearFlag(UART4,USART_FLAG_TC); uart4_trx_dma_config(); } } return 0; } void _usart_nvic_config(uint32_t usart_irqn, uint32_t preemptionpriority, uint32_t subpriority) { NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = usart_irqn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = preemptionpriority; NVIC_InitStructure.NVIC_IRQChannelSubPriority = subpriority; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(NVIC_InitStructure); } void _usart_comm_irqhandler(USART_TypeDef* usarttypedef,enum BOARD_USART_TYPE usart_type) { unsigned char recv_char; if (USART_GetFlagStatus(usarttypedef, USART_FLAG_RXNE) != RESET) { USART_ClearITPendingBit(usarttypedef, USART_IT_RXNE); recv_char = USART_ReceiveData(usarttypedef); if (_usart_recv_callbacks != NULL) _usart_recv_callbacks (usart_type, recv_char); } } void USART1_IRQHandler(void) { _usart_comm_irqhandler(USART1, BOARD_USART_1); } void USART2_IRQHandler(void) { _usart_comm_irqhandler(USART2, BOARD_USART_2); } void _usart_sendchar(USART_TypeDef* usart_typedef, uint8_t sChar) { USART_SendData(usart_typedef, (uint8_t) sChar); while (USART_GetFlagStatus(usart_typedef, USART_FLAG_TXE) == RESET) { } } void _usart_sendstring(USART_TypeDef* usart_typedef, uint8_t* string, uint8_t length) { uint8_t* p = string; uint8_t len; if(length==0){ while( *p++ != '\0' ){ USART_SendData(usart_typedef, (uint8_t)(*p)); while (USART_GetFlagStatus(usart_typedef, USART_FLAG_TXE) == RESET) { } } } else { for(len=0;lenlength;len++){ USART_SendData(usart_typedef, (uint8_t)(*p)); p++; while (USART_GetFlagStatus(usart_typedef, USART_FLAG_TXE) == RESET) { } } } } PUTCHAR_PROTOTYPE { USART_SendData(USART1, (u8) ch); while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); return ch; } /************************************************************* 串口中断 idle中断 *************************************************************/ void UART4_IRQHandler(void) { uint16_t dma_len; uint32_t i; uint16_t dmadatalength; if(USART_GetITStatus(UART4,USART_IT_IDLE)!=RESET) { DMA_Cmd(DMA2_Channel3,DISABLE); dma_len = DMA_GetCurrDataCounter(DMA2_Channel3); dmadatalength = UART4_DMA_BUF_NUM - dma_len; DMA_ClearFlag(DMA2_FLAG_GL3 | DMA2_FLAG_TC3 | DMA2_FLAG_TE3 | DMA2_FLAG_HT3);// DMA_SetCurrDataCounter(DMA2_Channel3,UART4_DMA_BUF_NUM); DMA_Cmd(DMA2_Channel3,ENABLE); usart_dma_dataprocess(uart4_rxdata,dmadatalength); } i = UART4-SR; i = UART4-DR; } // uart4 rx void DMA2_Channel3_IRQHandler(void) { DMA_ClearITPendingBit(DMA2_IT_TC3); DMA_ClearITPendingBit(DMA2_IT_TE3); DMA_Cmd(DMA2_Channel3,DISABLE); DMA_ClearFlag(DMA2_FLAG_GL3 | DMA2_FLAG_TC3 | DMA2_FLAG_TE3 | DMA2_FLAG_HT3); DMA_SetCurrDataCounter(DMA2_Channel3,UART4_DMA_BUF_NUM); DMA_Cmd(DMA2_Channel3,ENABLE); usart_dma_dataprocess(uart4_rxdata,UART4_DMA_BUF_NUM); } // uart4 tx void DMA2_Channel5_IRQHandler(void) { if(DMA_GetITStatus(DMA2_IT_TC5)){ DMA_ClearITPendingBit(DMA2_IT_GL5); } } void uart_dma_sendNbytes(uint8_t *buffer,uint16_t buffersize) { DMA_InitTypeDef DMA_InitStructure; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2,ENABLE); DMA_DeInit(DMA2_Channel5); DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(UART4-DR); DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)buffer; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; DMA_InitStructure.DMA_BufferSize = buffersize; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA2_Channel5, DMA_InitStructure); DMA_ITConfig( DMA2_Channel5, DMA_IT_TC, ENABLE ); DMA_ITConfig(DMA2_Channel5,DMA_IT_TE,ENABLE); USART_DMACmd(UART4,USART_DMAReq_Tx,ENABLE); DMA_Cmd(DMA2_Channel5, ENABLE); } void usart_dma_dataprocess(uint8_t *data,uint16_t datalen){ uart_dma_sendNbytes(data,datalen); } -----------------------------以上是.c文件-------------------------------------------- #ifndef __USART_H__ #define __USART_H__ #include stdio.h #include stm32f10x.h #include "boardconfig.h" /* * @brief USART总线编号 */ enum BOARD_USART_TYPE { BOARD_USART_1 = 1, BOARD_USART_2 = 2, BOARD_USART_3 = 3,BOARD_UART_4 }; typedef int (*USART_RECV_CALLBACK)(enum BOARD_USART_TYPE usart_no, unsigned char recv); struct USARTDefine { USART_TypeDef* usartdef; GPIO_TypeDef* pin_of_txd_group; uint16_t pin_of_txd; GPIO_TypeDef* pin_of_rxd_group; uint16_t pin_of_rxd; uint32_t bandrate; USART_RECV_CALLBACK recvcallback; }; /* @brief 使用UART初始化接口 @param config 配置数组 @param len 初始化的数量 @return 设置结果(0=SUCCESS, 1=FAILED) @desc 需要先设置NVIC (@ref BOARD_NVIC_Init) 和RCC */ extern uint32_t USART_Configuration(const struct USARTDefine* configs,uint16_t len); void _usart_sendchar(USART_TypeDef* usart_typedef, uint8_t sChar); extern void _usart_sendstring(USART_TypeDef* usart_typedef, uint8_t* string, uint8_t length); void uart_dma_sendNbytes(uint8_t *buffer,uint16_t buffersize); void usart_dma_dataprocess(uint8_t *data,uint16_t datalen); #endif ----------------------------------------以上是.h文件-------------------------------------- 问题 : 1)、如果设置的DMAbuffer 长度小于串口的接收帧长度,不同的中断方式什么效果? (uart idle是一个帧结束后,出现中断,而dma的中断则是定义的dmabuffer的长度到,进入中断,如果发送的帧长度大于定义的buffer长度,需要在程序中进行重新组帧操作) 2)、如果dmabuffer的长度大约串口的帧长度,可以结合使用uart 的idle方式 因为dma中断需要接收长度到达定义的长度时,才可以获取接收到的数据,此时可能帧已经结束,但是dma中断没有触发,有效的帧无通知信息告诉用户处理,但是如果使用uart的idle,则可以进入idle 中断,进行数据的帧处理。如果发送的数据长度大于dmabuffer的长度,也可以进行组帧操作结合idle中断进行。 定义的DMA的buffer大于帧长度,如何及时处理?参考:h ttp://www.stmcu.org/module/forum/thread-598094-1-1.html 1)使用定时器超时判断帧接收结束; 2)uart的起始位和停止位,利用定时器开启(起始位触发),复位(停止位触发),如果定时器超时,表示此帧结束
个人分类: smt32|4724 次阅读|0 个评论
分享 STM32-IAP学习笔记1---STM32 通过UART实现IAP功能 (在STM32F103C8T6验证通过)
热度 1 北北 2015-11-24 19:42
目录: 一、用UART方式实现IAP功能须知: 二、A、B工程具体功能为: 三、A、B工程代码分析: 四、NOTE: 五、该方式的不足: 一、用UART方式实现IAP功能须知: 需要两个工程文件。 一个作为初始工程,为了方便,我们叫A工程(bootloader);一个用于生成BIN文件的工程,我们叫B工程(可用于升级)。 二、 A、B工程具体功能为: A工程的功能: 检测升级条件是否被触发(可通过按键是否被按下、串口是否接收到特定的数据、U盘是否插入等等), 如果有, 则进行对user application(app区域)进行擦除和重新写入操作, 如果没有,则直接跳转到user application执行应用。 B工程的功能: 可升级A工程,应该用此工程制作出BIN文件,进而通过串口方式升级A工程。 三、A、B工程代码分析: 3.1、 A工程代码分析(只分析main函数和定时中断函数): int main(void) { LED_INIT(); //通过LED的状态验证升级是否成功。 //通过串口由PC发送升级BIN文件(MCU接收且将升级数据写入FLASH中)、通过串口软件显示从FLASH中读出的数据是否正确。 USART_Config(); KEY_Init(); //上电之前按着按键KEY1触发升级功能。 TIMER_Init(); //产生并进入中断,控制LED以不同方式闪烁,验证升级是否成功。 printf("\r\n -------系统已启动!------ \r\n"); LED1 = 0; LED1 = !LED1; while (1) { //若按下按键KEY1,触发进入更新FLASH的操作。 if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_12) == RESET) { Erase_SpecifiedPage(); //擦除APP区域。 //接收 NBR_UPDATEDATA 个字节的升级数据。 //(115200的波特率,每接收两个字节后进行一次FLASH半字写入)。 ReceiveUpdateData(NBR_UPDATEDATA); //延时以方便此时及时更改串口软件的配置,改为 HEX格式 以方便看数据。 printf("5S后发送读出的数据,请及时先清屏再将显示格式改为Hex格式。。 \r\n"); Delay_ms_72MHz(5000); //将写入的升级数据全部读出以检视数据是否正确。 Read_FLASH(ApplicationAddress, NBR_UPDATEDATA); } //若不按下按键,则执行app区域程序。 else { //若第一次执行且不触发升级操作,APP区域无数据,所以会无任何功能;若触发升级操作且升级成功, 则进入APP区域执行功能,下次复位会继续执行APP功能。 if (((*(__IO uint32_t*)ApplicationAddress) 0x2FFE0000 ) == 0x20000000) { JumpAddress = *(__IO uint32_t*) (ApplicationAddress + 4); Jump_To_Application = (pFunction) JumpAddress; __set_MSP(*(__IO uint32_t*) ApplicationAddress); Jump_To_Application(); } } } } void TIM2_IRQHandler(void) { static u8 Timer_Indictor_SYSOK = 0; if ( TIM_GetITStatus(TIM2 , TIM_IT_Update) != RESET ) { TIM_ClearITPendingBit(TIM2 , TIM_IT_Update); //清中断标志位。 Timer_Indictor_SYSOK++; if(Timer_Indictor_SYSOK == Timer_Indictor_SYSOK_PERIOD) { Timer_Indictor_SYSOK = 0; LED1 = !LED1; //LED1以1S的周期闪烁。 } } } 3.2、 B工程代码分析(只分析main函数和定时中断函数): int main(void) { //将APP程序的中断向量表进行偏移设置。定位到0x08004000处。 NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x4000); LED_INIT(); USART_Config(); KEY_Init(); TIMER_Init(); printf("\r\n IAP升级已成功!开始运行新程序。\r\n"); //提示新程序已正常运行。 printf("\r\n -----新系统已启动!------ \r\n"); LED1 = 0; LED2 = 0; while (1); } void TIM2_IRQHandler(void) { static u8 Timer_Indictor_SYSOK = 0; if ( TIM_GetITStatus(TIM2 , TIM_IT_Update) != RESET ) { TIM_ClearITPendingBit(TIM2 , TIM_IT_Update); //清中断标志位。 Timer_Indictor_SYSOK++; if(Timer_Indictor_SYSOK == Timer_Indictor_SYSOK_PERIOD) { Timer_Indictor_SYSOK = 0; LED1 = !LED1; //LED1以1S的周期闪烁。 LED2 = !LED2; //LED2以1S的周期闪烁。 } } } 四、NOTE: 4.1、 本应用是通过MDK-KEIL实现的,用到的MCU为STM32F103C8T6,调试器为ST-LINK。 4.2、 本程序成功地实现了IAP功能。将BIN文件下载到FLASH内并执行新功能。 本程序(即原始程序)的功能:LED1以1S的频率闪烁。 BIN文件的功能: LED1、LED2以1S的频率闪烁。 4.3、 具体实现方法: 当板子开始上电即程序开始运行前,按着KEY1按键,然后上电,此时有提示:擦除完毕,请发送升级数据。 发送BIN文件,然后有提示:已经接收完毕升级数据并写入FLASH中,稍后读出升级数据。 当读出数据后(可通过串口软件查看数据是否正确),新程序开始运行(通过观看LED状态知升级成功)。 4.4、 对于A工程: 跟普通程序一样,设置KEIL中魔术棒中的target中勾选IROM1,并配置其后的数据为 0x08000000,即程序定位在FLASH的最开始处(IAP程序)。 对于B工程: 当作为一般文件时,跟普通程序一样,注意:此时可正常调试。 当作为升级文件时,注意:不能正常调试,只能作为升级文件使用(我们要将其作为升级文件)。 其需要修改的地方: 1. 在其程序开始处,添加中断偏移设置,即:NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x4000); (0x4000,可根据实际情况调节。) 2. 在魔术棒中的target中勾选IROM1,并配置其后的数据为 0x08004000 (对应上面) 。 其他无需修改。 3. 关于如何生成BIN文件,参看BIN文件生成方法。 0x080000处 0x08004000处 -------------------------------------------------------------------------------- |IAP区域。。。。。。|APP区域。。。。。。。。。。。。。。。| -------------------------------------------------------------------------------- 五、该方式的不足: 出厂前必须先烧录IAP引导程序,再通过串口方式升级代码至出厂时需要的功能(麻烦!)。 若只烧录IAP引导程序,无任何功能! 有没有更好的IAP方式,待研究。 六、待完善。
个人分类: STM32|9456 次阅读|1 个评论
分享 基于FPGA的UART驱动设计
TT小哥 2015-8-7 17:50
基于 FPGA 的串口驱动设计 基于 FPGA 的串口驱动设计 1 设计需求 本设计预完成基于 FPGA 的串口驱动程序的编写,可实现 FPGA 与串口设备的数据传输。本设计拟以 3 实验完成串口驱动的编写、测试以及使用实例。 2 设计内容 2.1 设计原理 2.1.1 串口通信 串口传输是一种通用的两线制全双工通信方式,其协议如下: 其传输与接收都不时钟同步,为异步通信方式,在通信之前收发双方必须确认通信的频率(常称为比特率),其具体时序传输过程已发送举例如下时序: 图 1 串口时序 总线常态为逻辑“ 1 ”高电平,在有数据传输时发送端将信号线拉低,接收检测到下降沿即开始接收数据,电平的改变频率受预先定义的比特率控制,传输 8 位数据(串口数据位数也有 5 、 6 、 7 等不同类,常用 8 位)后传输 1 位奇(偶)校验位亦可依据协议省略不发,最后输出上升沿停止位将总线置高,设为常态,等待下次下降沿起始位的触发。接收则是依据发送时序进行采样。 2.1.2FIFO 存储器 FIFO 常用于通信缓冲器,与通常 RAM 不同的是,其没有地址线数据,输出方式采用先入先出的方式( FIFO--FirstInFirstOut ),正常的工作模式是在输入使能条件下,存储器每一个时钟边沿触发就会存储一个数据,其读指针同时也会发生偏移,在输出使能的条件下每个时钟边沿触发就出读出一个数据,其写指针同时也会发生偏移,多用在数据的缓冲作用。其模型如图 2 所示,初始状态下读写指针都指定在 0x00 上,此时 FIFO 状态为空,当读使能信号使能,并保持一个时钟( FIFO 读写时钟)周期,数据将通过数据输入端存储到 0x00 读指针也偏移到 0x01 ,依次类推。当 FIFO 中有数据(非空)时,写使能有效并保持一个时钟, FIFO 将会输出写指针所指的存储空间的数据,完成先入先出的效果。 图 2FIFO 示意图 2.2 参考文档 在撰写该文档并实验之前,笔者先拜读了特权、小梅哥以及黑金原创的教程。 2.2.1 特权 FPGA 实验实现与理解 特权同学的串口代码目前是网上流传最为广泛的版本。在拿到特权同学的代码,首先打开工程编译了一下,通过了!由于习惯问题,先看看其 RTL 视图如图 3 所示。特权果然大神,其代码结构十分明显,主要哟四个模块组成分别是发送波特率发生器、发送模块、 接收波特率发生器 以及接受模块。其中发送波特率发生器与接收波特率发生器的逻辑完全一样,采用同一个模块文件例化而成(特权同学特别说明:不是资源共享,和软件中的同一个子程序调用不能混为一谈,可称之为逻辑复制)。 图 3 特权 UART 驱动 RTL 视图 特权的代码经过综合后,下载到我的板子上测试,果然可以完成单字节的收发(其功能是接收串口发送一个字节的数据后并返回)。特权同学的各模块接口定义如表 1 所示: speed_select 模块接口: 接口名称 位宽 描述 clk 1 工作时钟 rst_n 1 异步复位信号,低电平有效 bps_start 1 波特率同步使能信号 clk_bps 1 波特率脉冲信号输出口 my_uart_rx 模块接口: 接口名称 位宽 描述 clk 1 工作时钟 rst_n 1 异步复位信号,低电平有效 rs232_rx 1 串口接收信号线 rx_data 8 接收数据 8 位并行输出接口 rx_int 1 接收数据中断 , 接收到数据期间始终为高电平 clk_bps 1 波特率脉冲信号输入 bps_start 1 波特率发生器使能,在接收过程中始终为高电平 my_uart_tx 模块接口: 接口名称 位宽 描述 clk 1 工作时钟 rst_n 1 异步复位信号,低电平有效 rs232_ t x 1 串口接收信号线 t x_data 8 发送 8 为数据并行输入接口 t x_int 1 发送中断请求,下降沿开始发送串口数据 clk_bps 1 波特率脉冲信号输入 bps_start 1 波特率发生器使能,在接收过程中始终为高电平 (说明:在特权的原本代码中 t x_int 、 t x_data 名称为 r x_int 、 r x_data 为了与上发送模块的接口区分,稍作了修改。) 在了解了各模块接口之后,我们基本上可以使用该驱动了。返回 top 层的 RTL 视图观察该实验的连接关系,笔者发现了一个问题。笔者将每个模块视为一个个黑匣子,只根据以上的接口功能对实验进行分析。首先上电之后,接收模块接收数据,该段时间 my_uart_rx 中的 r x_int 、 bps_start 会为高电平,波特率发生器产生信号,接收正常;此时由于 my_uart_tx 模块没有接收到其接口 t x_int 的下降沿,处于等待状态。当数据接收完毕后, my_uart_rx 中的 rx_int 、 bps_start 会被拉低,而 t x_int 与 rx_int 的连接关系会触发 my_uart_ t x 发送数据,而后 my_uart_rx 可以继续等待并接收数据。理论上,在 my_uart_rx 接收了第二个字符后, my_uart_ t x 也发送完了第一个字符。这样就形成了一个数据通路,以这种状态足以完成多字节的传输与返回。这种状态需要个条件,就是发送速度大于或者等于接收速度(在两者相等时,发送结束到接收到发送请求必须要有足够的响应时间)。 指出了上面的问题,查看模块内部的具体代码: 波特率发生器代码: module speed_select ( clk , rst_n , bps_start , clk_bps ); input clk ; //50MHz 主时钟 input rst_n ; // 低电平复位信号 input bps_start ; // 接收到数据后,波特率时钟启动信号置位 output clk_bps ; //clk_bps 的高电平为接收或者发送数据位的中间采样点 /* parameterbps9600=5207,// 波特率为 9600bps bps19200=2603,// 波特率为 19200bps bps38400=1301,// 波特率为 38400bps bps57600=867,// 波特率为 57600bps bps115200=433;// 波特率为 115200bps parameterbps9600_2=2603, bps19200_2=1301, bps38400_2=650, bps57600_2=433, bps115200_2=216; */ // 以下波特率分频计数值可参照上面的参数进行更改 `define BPS_PARA 5207 // 波特率为 9600 时的分频计数值 `define BPS_PARA_2 2603 // 波特率为 9600 时的分频计数值的一半,用于数据采样 reg cnt ; // 分频计数 reg clk_bps_r ; // 波特率时钟寄存器 //---------------------------------------------------------- reg uart_ctrl ; //uart 波特率选择寄存器 //---------------------------------------------------------- always @ ( posedge clk or negedge rst_n ) if (! rst_n ) cnt = 13'd0 ; else if (( cnt == `BPS_PARA ) || ! bps_start ) cnt = 13'd0 ; // 波特率计数清零 else cnt = cnt + 1'b1 ; // 波特率时钟计数启动 always @ ( posedge clk or negedge rst_n ) if (! rst_n ) clk_bps_r = 1'b0 ; else if ( cnt == `BPS_PARA_2 ) clk_bps_r = 1'b1 ; //clk_bps_r 高电平为接收数据位的中间采样点 , 同时也作为发送数据的数据改变点 else clk_bps_r = 1'b0 ; assign clk_bps = clk_bps_r ; endmodule 1~9 行:为模块的接口定义,个接口功能在上文已说明,不再赘述; 12~27 行:以注释的方式给我们说明了各个不同波特率的计数值,该值只在 50Mhz 的时钟平率下有效,具体计算以 9600bps 举例为何为 5207 ,详情阅读下面代码说明。 37~41 行:异步清零同步使能计数器的典型设计,在 50Mhz 时钟驱动下,计数器会在使能情况下从 0 计数到达 5207 后清零再次计数,这样 0~5207 计数时间为 5208*20ns=104160ns ;计算一下 9600Hz 的周期 1000,000,000ns/9600=104166ns 。两者基本相同。 43~47 行:在 0~5207 计数到中间值 2603 时,输出一个脉冲信号,其频率依然是 9600 。这里为何要在其中间值产生一个脉冲,而不是在开始计数时,也不是在计数之后产生呢?特权同学是有其道理的!!!下文会对其进一步分析。 发送模块代码: module my_uart_tx ( clk , rst_n , rx_data , rx_int , rs232_tx , clk_bps , bps_start ); input clk ; //50MHz 主时钟 input rst_n ; // 低电平复位信号 input clk_bps ; //clk_bps_r 高电平为接收数据位的中间采样点 , 同时也作为发送数据的数据改变点 input rx_data ; // 接收数据寄存器 input rx_int ; // 接收数据中断信号 , 接收到数据期间始终为高电平 , 在该模块中利用它的下降沿来启动串口发送数据 output rs232_tx ; //RS232 发送数据信号 output bps_start ; // 接收或者要发送数据,波特率时钟启动信号置位 //--------------------------------------------------------- reg rx_int0 , rx_int1 , rx_int2 ; //rx_int 信号寄存器,捕捉下降沿滤波用 wire neg_rx_int ; //rx_int 下降沿标志位 always @ ( posedge clk or negedge rst_n ) begin if (! rst_n ) begin rx_int0 = 1'b0 ; rx_int1 = 1'b0 ; rx_int2 = 1'b0 ; end else begin rx_int0 = rx_int ; rx_int1 = rx_int0 ; rx_int2 = rx_int1 ; end end assign neg_rx_int = ~ rx_int1 rx_int2 ; // 捕捉到下降沿后, neg_rx_int 拉高保持一个主时钟周期 //--------------------------------------------------------- reg tx_data ; // 待发送数据的寄存器 //--------------------------------------------------------- reg bps_start_r ; reg tx_en ; // 发送数据使能信号,高有效 reg num ; always @ ( posedge clk or negedge rst_n ) begin if (! rst_n ) begin bps_start_r = 1'bz ; tx_en = 1'b0 ; tx_data = 8'd0 ; end else if ( neg_rx_int ) begin // 接收数据完毕,准备把接收到的数据发回去 bps_start_r = 1'b1 ; tx_data = rx_data ; // 把接收到的数据存入发送数据寄存器 tx_en = 1'b1 ; // 进入发送数据状态中 end else if ( num == 4'd11 ) begin // 数据发送完成,复位 bps_start_r = 1'b0 ; tx_en = 1'b0 ; end end assign bps_start = bps_start_r ; //--------------------------------------------------------- reg rs232_tx_r ; always @ ( posedge clk or negedge rst_n ) begin if (! rst_n ) begin num = 4'd0 ; rs232_tx_r = 1'b1 ; end else if ( tx_en ) begin if ( clk_bps ) begin num = num + 1'b1 ; case ( num ) 4'd0 : rs232_tx_r = 1'b0 ; // 发送起始位 4'd1 : rs232_tx_r = tx_data ; // 发送 bit0 4'd2 : rs232_tx_r = tx_data ; // 发送 bit1 4'd3 : rs232_tx_r = tx_data ; // 发送 bit2 4'd4 : rs232_tx_r = tx_data ; // 发送 bit3 4'd5 : rs232_tx_r = tx_data ; // 发送 bit4 4'd6 : rs232_tx_r = tx_data ; // 发送 bit5 4'd7 : rs232_tx_r = tx_data ; // 发送 bit6 4'd8 : rs232_tx_r = tx_data ; // 发送 bit7 4'd9 : rs232_tx_r = 1'b1 ; // 发送结束位 default : rs232_tx_r = 1'b1 ; endcase end else if ( num == 4'd11 ) num = 4'd0 ; // 复位 end end assign rs232_tx = rs232_tx_r ; endmodule 相较于接收,发送总是更为简单,因为串口的发送只是按一定频率将数据按协议的模式一步一步发出去就完事了,而接收则需要判断什么时候采样,需要去考虑发送方的时序。 特权同学串口发送模块 1~15 行:为接口的定义说明,上文已作说明,不再赘述; 17~34 行:为一个电平检测电路,通过 4 个寄存器 rx_int0 , rx_int1 , rx_int2 对 rx_int 的电平进行缓存,根据缓存的状态判断边沿。 如: assign neg_rx_int = ~ rx_int 0 ~ rx_int1 rx_int2 rx_int 3 ; // 捕捉下降沿, neg_rx_int =1 assign neg_rx_int = rx_int 0 rx_int1 ~ rx_int2 ~ rx_int 3 ; // 捕捉到 上升 沿, neg_rx_int =1 当然特权同学只是用 2 个寄存器也是同样的原理,可以有一定的灵明度提升。采用多个寄存器缓存判断边沿触发是一种时间换稳定性的方法。 37~59 行:控制波特率发生器使能语句,在检测到 rx_int 的下降沿后,使能波特率发生器、将 tx_en 设置为 1 以及锁存外部输入的传输数据。只有在寄存器 num 计数到 11 时才停止波特率和数据的发送。 64~92 行: 此段代码为本模块的核心内容,其就是一个有限状态机(小梅哥称之为有限序列机,小弟无法理解两者区别,姑且称之为状态机)。 该状态机受控于两个信号 tx_en 和 clk_bps ,其中 tx_en 将会在接收发送请求后被置 1 , clk_bps 也会随着脉冲发生器被使能后,输出 9600hz 的脉冲信号。当发送开始后每一次脉冲 num 都会自增 1, 且 num 为 0 时,输出起始位; num 为 1 时输出数据 0 位; ...... num 为 8 时输出数据 7 位; num 为 9 时输出停止位; 计数到 11 时停止发送总共会产生 11 个脉冲信号,这里就是问题了, 1 位起始位 +8 位数据位 +1 位停止位总共 10 位数据为何需要 11 个脉冲信号操纵呢!这里就是导致为什么该驱动只能接收但个字节而不能接受多个字节。由于其在发送后有多等待了一个周期,拖慢了传输的速率,影响到整个信道的流畅性。修改 55 行和 88 行代码为 55 else if ( num == 4'd1 0 ) begin // 数据发送完成,复位 88 else if ( num == 4'd11 ) num = 4'd0 ; // 复位 将 11 改为 10 在测试能否进行多字节的收发,测试发现还是无法达到目标,继续看看接收部分。 接收模块代码: module my_uart_rx ( clk , rst_n , rs232_rx , rx_data , rx_int , clk_bps , bps_start ); input clk ; //50MHz 主时钟 input rst_n ; // 低电平复位信号 input rs232_rx ; //RS232 接收数据信号 input clk_bps ; //clk_bps 的高电平为接收或者发送数据位的中间采样点 output bps_start ; // 接收到数据后,波特率时钟启动信号置位 output rx_data ; // 接收数据寄存器,保存直至下一个数据来到 output rx_int ; // 接收数据中断信号 , 接收到数据期间始终为高电平 //---------------------------------------------------------------- reg rs232_rx0 , rs232_rx1 , rs232_rx2 , rs232_rx3 ; // 接收数据寄存器,滤波用 wire neg_rs232_rx ; // 表示数据线接收到下降沿 always @ ( posedge clk or negedge rst_n ) begin if (! rst_n ) begin rs232_rx0 = 1'b0 ; rs232_rx1 = 1'b0 ; rs232_rx2 = 1'b0 ; rs232_rx3 = 1'b0 ; end else begin rs232_rx0 = rs232_rx ; rs232_rx1 = rs232_rx0 ; rs232_rx2 = rs232_rx1 ; rs232_rx3 = rs232_rx2 ; end end // 下面的下降沿检测可以滤掉 20ns-40ns 的毛刺 ( 包括高脉冲和低脉冲毛刺 ) , // 这里就是用资源换稳定(前提是我们对时间要求不是那么苛刻,因为输入信号打了好几拍) // (当然我们的有效低脉冲信号肯定是远远大于 40ns 的) assign neg_rs232_rx = rs232_rx3 rs232_rx2 ~ rs232_rx1 ~ rs232_rx0 ; // 接收到下降沿后 neg_rs232_rx 置高一个时钟周期 //---------------------------------------------------------------- reg bps_start_r ; reg num ; // 移位次数 reg rx_int ; // 接收数据中断信号 , 接收到数据期间始终为高电平 always @ ( posedge clk or negedge rst_n ) if (! rst_n ) begin bps_start_r = 1'bz ; rx_int = 1'b0 ; end else if ( neg_rs232_rx ) begin // 接收到串口接收线 rs232_rx 的下降沿标志信号 bps_start_r = 1'b1 ; // 启动串口准备数据接收 rx_int = 1'b1 ; // 接收数据中断信号使能 end else if ( num == 4'd12 ) begin // 接收完有用数据信息 bps_start_r = 1'b0 ; // 数据接收完毕,释放波特率启动信号 rx_int = 1'b0 ; // 接收数据中断信号关闭 end assign bps_start = bps_start_r ; //---------------------------------------------------------------- reg rx_data_r ; // 串口接收数据寄存器,保存直至下一个数据来到 //---------------------------------------------------------------- reg rx_temp_data ; // 当前接收数据寄存器 always @ ( posedge clk or negedge rst_n ) if (! rst_n ) begin rx_temp_data = 8'd0 ; num = 4'd0 ; rx_data_r = 8'd0 ; end else if ( rx_int ) begin // 接收数据处理 if ( clk_bps ) begin // 读取并保存数据 , 接收数据为一个起始位, 8bit 数据, 1 或 2 个结束位 num = num + 1'b1 ; case ( num ) 4'd1 : rx_temp_data = rs232_rx ; // 锁存第 0bit 4'd2 : rx_temp_data = rs232_rx ; // 锁存第 1bit 4'd3 : rx_temp_data = rs232_rx ; // 锁存第 2bit 4'd4 : rx_temp_data = rs232_rx ; // 锁存第 3bit 4'd5 : rx_temp_data = rs232_rx ; // 锁存第 4bit 4'd6 : rx_temp_data = rs232_rx ; // 锁存第 5bit 4'd7 : rx_temp_data = rs232_rx ; // 锁存第 6bit 4'd8 : rx_temp_data = rs232_rx ; // 锁存第 7bit default : ; endcase end else if ( num == 4'd12 ) begin // 我们的标准接收模式下只有 1+8+1(2)=11bit 的有效数据 num = 4'd0 ; // 接收到 STOP 位后结束 ,num 清零 rx_data_r = rx_temp_data ; // 把数据锁存到数据寄存器 rx_data 中 end end assign rx_data = rx_data_r ; endmodule 1~15 行:为接口的定义说明,上文已作说明,不再赘述; 17~36 行:为一个电平检测电路,通过 4 个寄存器 rx_int0 , rx_int1 , rx_int2 对 rx_int 的电平进行缓存,根据缓存的状态判断边沿。原理与上文相同,只是换了一个信号的输入口而已,这里不再赘述。 40~60 行:以串口的起始位作为信号,开启波特率发生器和接收中断信号。与前文略有相同,可对比理解,不在赘述。 62~100 行:此段代码为本模块的核心内容,其就是一个有限状态机。其结构大体与上文的发送代码极其相似,只有 79~95 有所不同,只对其进行分析。仔细看 79 行代码,与一般的状态机不同,不是从 0 开始的!分析串口时序图: 图 4 三中不同脉冲触发方式时序图 虽说 UART 是异步传输,但是其还是有一定时钟节拍,只是没有通过信号输出来同步而已,在发送与接收双方都会产生一个时钟,作为基准。图 4 所示,比如发送端在上升沿进行数据的改变,在第一个时钟的上升沿产生起始信号, FPGA 开始使能波特率发生器产生一定频率的脉冲,三种不同的脉冲产生形式对应如图 4 。只有在中值时产生的脉冲正巧对应着发送时钟的下降沿,且处于两个数据改变边沿正中央,是个绝佳的采样点。而第一个时钟是起始位、最后一个时钟是停止位,故在代码中 num =0 与 num =9 多没有对信号进行采样。但问题又来了,与前文相同 90 行代码, num =12 时才结束接收,多等待了两个时钟,这段等待的时间在连续发送的多字节时,肯定会错过下一个字节的起始位和第 0 位数据。这就解释了上面代码更改了之后还是无法进行多字节的收发。用事实说话,做个小实验。 首先我们不改变接收代码只改变发送代码,采用 16 进制发送 0x11,0x00,0x22,0x00,0x33,0x00,0x44,0x00,0x55,0x00,0x66,0x00 。如果笔者分析正确,驱动一定会丢失偶数位的代码(由于没有接收到起始位,不会启动接收),偶数位发送 0x00 是为了让 1~7 位的数据变化被驱动错认为是启动信号,当然 0xff 也是可行的,只有不出现下降沿,实验依然可行。 图 5 测试图 测试结果与预期相同,进一步证明分析正确。修改 55 行和 90 行代码为 55 else if ( num == 4'd1 0 ) begin // 接收完有用数据信息 90 else if ( num == 4'd1 0 ) begin // 我们的标准接收模式下只有 图 6 特权代码实现多字节传输实验图 发送字节数与接收字节数完全相同,没有丢码和误码现象。到此特权同学的代码分析与实现到此结束。在此感谢特权同学的无私分享! 2.2.2 小梅哥教程实验 小梅哥的代码是以特权同学的代码为基础,在上文的 2 个重点上进行了修改,并且在内容的一些细节上加以完善,代码可直接综合跑通,串口的字节接收并发送的实验。其代码的大体结构与框架并未进行大的改变,不予细读了。在小梅哥的教程中利用了强大的逻辑仿真软件 modlesim 对代码进行了测试,再进行板级调试。这是大多数教程中并没有涉及到的东西,当接触后才发觉, FPGA 编程根本离不开逻辑仿真工具。详情请参阅 《小梅哥和你一起深入学习 FPGA 之串口调试》 。 2.2.3 黑金教程 黑金的教程在网上的一些 FPGA 教程中是比较系统的。其中《 verilog 那些事儿 - 驱动篇 I 》中实验 12~15 都是简述 FPGA 实现串口通信的教程。 实验十二 串口模块 1-- 发送 实验十三 串口模块 2-- 接收 实验十四 存储模块 实验十五 FIFO 存储模块(同步) 实验十二串口模块 1-- 发送: 驱动代码主要分文两个模块,一个是串口的发送模块另一个是顶层模块。在黑金的文档中其建模将两个模块分为功能模块和控制模块,这样的结构分类可以让整个勾结更为清晰,相信在较为复杂的设计中会体现其优势(本人臆测尚未有待证实)。图 7 所示为功能模块 RTL 视图: 图 7 功能模块 RTL 视图 接口定义: 接口名称 位宽 描述 CLOCK 1 工作时钟入口 RESET 1 异步复位信号,低电平有效 iCall 1 请求发送数据信号,可视为同步使能 iData 8 8 位数据传输输入模块 TXD 1 串口发送数据通信线 oDone 1 发送结束信号 在了解接口定义后,可直接调用该模块进行串口数据的发送了,其控制流程为: iData 输入待传输数据,而后将 iCall 信号线拉高,模块即进入发送模式,在 oDone 返回一个脉冲之后,表明发送结束,拉低 iCall 完成一次传输过程。 控制模块代码 module tx_demo ( input CLOCK , RESET , output TXD ); wire DoneU1 ; tx_funcmodU1 ( . CLOCK ( CLOCK ), . RESET ( RESET ), . TXD ( TXD ), . iCall ( isTX ), . oDone ( DoneU1 ), . iData ( D1 ) ); reg i ; reg D1 ; reg isTX ; always @ ( posedge CLOCK or negedge RESET ) if ( ! RESET ) begin i = 4'd0 ; D1 = 8'd0 ; isTX = 1'b0 ; end else case ( i ) 0 : if ( DoneU1 ) begin isTX = 1'b0 ; i = i + 1'b1 ; end else begin isTX = 1'b1 ; D1 = 8'hA1 ; end 1 : if ( DoneU1 ) begin isTX = 1'b0 ; i = i + 1'b1 ; end else begin isTX = 1'b1 ; D1 = 8'hA2 ; end 2 : if ( DoneU1 ) begin isTX = 1'b0 ; i = i + 1'b1 ; end else begin isTX = 1'b1 ; D1 = 8'hA3 ; end 3 : //Stop i = i ; endcase endmodule 对比特权串口的代码,该模块类似于特权的顶层模块。两者有一定的区别,黑金的代码思想更接近于实践的控制,顶层同样采用一种状态机的控制逻辑完成实验目的,在实际的串口使用控制模式同样是可以使用这样的模式,只是更改状态机的逻辑而已,而特权同学的代码则更注重于测试目的。 22~46行:完成3个字节的发送,而后停止控制。 从控制模块的控制来看,逻辑简单就可完成串口驱动的控制,同样表明了给接口定义的科学性。 更能模块: module tx_funcmod ( input CLOCK , RESET , output TXD , input iCall , output oDone , input iData ); parameter B115K2 = 9'd434 ; //formula:(1/115200)/(1/50E+6) reg i ; reg C1 ; reg D1 ; reg rTXD ; reg isDone ; always @( posedge CLOCK or negedge RESET ) if ( ! RESET ) begin i = 4'd0 ; C1 = 9'd0 ; D1 = 11'd0 ; rTXD = 1'b1 ; isDone = 1'b0 ; end else if ( iCall ) case ( i ) 0 : begin D1 = { 2'b11 , iData , 1'b0 }; i = i + 1'b1 ; end 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 : if ( C1 == B115K2 - 1 ) begin C1 = 8'd0 ; i = i + 1'b1 ; end else begin rTXD = D1 ; C1 = C1 + 1'b1 ; end 12 : begin isDone = 1'b1 ; i = i + 1'b1 ; end 13 : begin isDone = 1'b0 ; i = 4'd0 ; end endcase assign TXD = rTXD ; assign oDone = isDone ; endmodule 发送模块总过只需48行代码,而且一个所有逻辑融合在一个状态机中。 1~15行:接口定义,不再赘述。 11~46行:一个状态机完成了串口驱动的逻辑功能。状态机仅受控于一个同步使能信号iCall,同样也是控制模块的控制信号。 状态寄存器i=0:完成发送数据的锁存,发送的模式为1+8+2的11位数据,加上了校验位,但是校验位并没有使用,所有的校验位均只默认发送1,这样在发送端会降低发送速率,在多字节发送时有可能出现与特权同学相同的问题。 状态寄存器i=1~11: 发送11位数据,采用计数方式让每个数据保持 B115K2 个时钟周期时间,该数值具体需更具波特率计算。与特权的代码思想上略有不同,前者采用的时序触发的思维模式,在时序图上可以清晰的看出逻辑,后者从电平的保持时间来作为切入点,从时序逻辑上看,特权的思维感觉更为容易理解。 状态寄存器i=12~13:完成一个结束指令输出。 分析完代码,采用逻辑仿真工具看一下时序逻辑,编写基础的 testbench 文件,由于发送模块的一些控制指令在,控制模块上已经给出了控制信号,在 testbench 中只需提供复位和时钟信号即可。 关于 modelsim 的操作,参阅《小梅哥和你一起深入学习 FPGA 之串口调试》解释的极为详尽! testbench 代码: `timescale 1ns / 1ns module testbench ; reg clk ; reg rst_n ; wire TXD ; initial begin clk = 1'b0 ; rst_n = 1'b0 ; # 200 rst_n = 1'b1 ; end always # 10 clk = ~ clk ; tx_demotx_demo ( . CLOCK ( clk ), . RESET ( rst_n ), . TXD ( TXD ) ); endmodule 该部分代码极为简单,只是在 16 行提供了一个时钟操作和 11~12 行进行了一个复位操作。需要说明的是,在 modelsim 中其实一个软件模拟方式,故在导入硬件描述语言的时候有可能会出现类似于 c 语言中报错没有定义的情况,需要调整寄存器的定义位置,比如在 tx_demo 中 19 、 20 行定义了 D1 和 isTX 但是在之前 13 、 15 行已经使用了这两个变量,在 Quartus 中编译综合都没有问题,但在 modelsim 中就可能会报错无法编译。需要将其定义移动到使用之前即可。 图 8modelsim 未定义报错 图 9 串口发送 图 9 红色方框中的每个 11 个周期时间,对应的 TXD 电平依次是 11'b01000010111 、 11'b00100010111 、 11'b01100010111 ;与发送逻辑符合(下降沿起始信号 + 0xa1 ( 0xa2 、 0xa3 ) +1( 校验位 )+ 上升沿停止位)。 逻辑仿真查看逻辑正常后,在进行板级测试,基本上若是在仿真器上的逻辑跑通了,板级测试不会有太大问题(低速应用)。图 10 显示,在 FPGA 上电后,发送了 0xa1 、 0xa2 、 0xa3 三个十六进制数给上位机,测试成功。 图 10 板级测试图 实验十三串口模块 2-- 接收 此模块代码在黑金的文档中有详尽的解析,其中对串口接收中时钟跟踪的理解方法很是到位。详情参阅黑金 verilog 那些事儿 - 驱动篇 I 《驱动篇 I- 实验 13 》。 代码阅读之后,进行逻辑仿真更为直观的观察逻辑是否正确。由于要测试接收模块,所以除了必要的时钟激励和复位信号以外,还要有串口输入信号。在编写 testbench 之前,采用不可综合逻辑语言写出 串口模块的模型 用于测试,代码如下: `timescale 1ns / 1ns module UART_module ( RX , TX ); input RX ; output TX ; reg TX ; reg data_buf ; task transmission ; input tdata ; begin # 8680 TX = 1'b0 ; # 8680 TX = tdata ; # 8680 TX = tdata ; # 8680 TX = tdata ; # 8680 TX = tdata ; # 8680 TX = tdata ; # 8680 TX = tdata ; # 8680 TX = tdata ; # 8680 TX = tdata ; # 8680 TX = 1'b1 ; # 8680 TX = 1'b1 ; end endtask task receive ; output rdata ; begin @( negedge RX ) # 8680 ; # 4340 rdata = RX ;# 4340 ; # 4340 rdata = RX ;# 4340 ; # 4340 rdata = RX ;# 4340 ; # 4340 rdata = RX ;# 4340 ; # 4340 rdata = RX ;# 4340 ; # 4340 rdata = RX ;# 4340 ; # 4340 rdata = RX ;# 4340 ; # 4340 rdata = RX ;# 4340 ; # 8680 ; # 8680 ; end endtask initial begin TX = 1'b1 ; data_buf = 8'h55 ; transmission ( 8'h0f ); receive ( data_buf ); transmission ( 8'hf0 ); receive ( data_buf ); transmission ( 8'haa ); receive ( data_buf ); transmission ( 8'h55 ); receive ( data_buf ); end endmodule 对于可综合 VerilogHDL 而言,不可综合的 VerilogHDL 语言书写更加随意,并且可以采用顺序语言的思想进行书写。由于习惯于 C 语言的代码思想,在进行串口的建模时,采用的顺序思想进行编写。 11~26 行:表示发送任务,每延时 8680ns 后输出信号电平; 28~42 行:表示接收任务, 31 行代码表示等待 RX 下降沿的触发,而后便是依据 115200 的比特率进行采样信号。 在初始化语句中( 45~47 行),调用了任务并初始化了一些信号线,该部分的代码顺序执行。先发送 8'h0f 再等待接受,而后发送 8'hf0 等待接受 1 字节数据,再发送 8'haa 等待接收 1 字节数据,最后发送 8'h55 等待接收 1 字节数据。 顶层 testbench 例化待测试模块以及串口模块的模型: `timescale 1ns / 1ns module testbench ; reg clk ; reg rst_n ; initial begin clk = 1'b0 ; rst_n = 1'b0 ; # 10 rst_n = 1'b1 ; end always # 10 clk = ~ clk ; rx_demorx_demo ( . CLOCK ( clk ), . RESET ( rst_n ), . RXD ( TX ), . TXD ( RX ) ); UART_moduleUART_module ( . RX ( RX ), . TX ( TX ) ); endmodule testbench 提供时钟并例化模块,仿真逻辑图如图 11 : 图 11 串口接收测试模型 有仿真结果可知,串口收发 4 组数据,逻辑并未出错。但是这种收发室单字节,每个传输字节之间有一个字节传输的等待时间,意思就是作为全双工通信的总线的串口只是工作在单工的模式。修改黑金的代码,看能否像特权的驱动一般实现在多字节传输。首先要修改合金的顶层例化文件,将发送模块与接收模块分开例化,使其可以完成同时工作状态,: module rx_demo ( input CLOCK , RESET , input RXD , output TXD ); wire DoneU1 , DoneUT ; wire DataU1 ; reg TData ; reg i ; reg C1 ; reg D1 ; reg isRX , isTX ; /*------------------------------------------------------*/ rx_funcmodU1 ( . CLOCK ( CLOCK ), . RESET ( RESET ), . RXD ( RXD ), //top . iCall ( isRX ), //core . oDone ( DoneU1 ), //core . oData ( DataU1 ) //core ); /*------------------------------------------------------*/ tx_funcmodUT ( . CLOCK ( CLOCK ), . RESET ( RESET ), . TXD ( TXD ), . iCall ( isTX ), . oDone ( oDoneUT ), . iData ( TData ) ); /*------------------------------------------------------*/ parameter B115K2 = 9'd434 , TXFUNC = 5'd16 ; always @ ( posedge CLOCK or negedge RESET ) if ( ! RESET ) begin i = 5'd0 ; C1 = 9'd0 ; D1 = 11'd0 ; isRX = 1'b1 ; end else case ( i ) 0 : if ( DoneU1 ) begin i = i + 1'b1 ; TData = DataU1 ; end 1 : begin i = i + 1'b1 ; isTX = 1'b1 ; end 2 : if ( oDoneUT ) begin i = 5'd0 ; isTX = 1'b0 ; end endcase endmodule 26~34 行:例化了串口发送驱动,使用实验十二串口发送代码。 40~60 行:修改了控制模块的状态机串口的接收使能控制 isRX 始终置高,只有有数据传入就可接受,在每次接收完一个数据后,启动发送指令发送一个数据。其基本的思想和特权代码的例化顶层大同小异,仿真测试。 修改串口模型: `timescale 1ns / 1ns module UART_module ( RX , TX ); input RX ; output TX ; reg TX ; reg data_buf ; task transmission ; input tdata ; begin # 8680 TX = 1'b0 ; # 8680 TX = tdata ; # 8680 TX = tdata ; # 8680 TX = tdata ; # 8680 TX = tdata ; # 8680 TX = tdata ; # 8680 TX = tdata ; # 8680 TX = tdata ; # 8680 TX = tdata ; # 8680 TX = 1'b1 ; # 8680 TX = 1'b1 ; end endtask task receive ; output rdata ; begin @( negedge RX ) # 8680 ; # 4340 rdata = RX ;# 4340 ; # 4340 rdata = RX ;# 4340 ; # 4340 rdata = RX ;# 4340 ; # 4340 rdata = RX ;# 4340 ; # 4340 rdata = RX ;# 4340 ; # 4340 rdata = RX ;# 4340 ; # 4340 rdata = RX ;# 4340 ; # 4340 rdata = RX ;# 4340 ; # 8680 ; # 8680 ; end endtask initial begin TX = 1'b1 ; data_buf = 8'h55 ; transmission ( 8'h0f ); transmission ( 8'hf0 ); transmission ( 8'haa ); transmission ( 8'h55 ); end initial begin receive ( data_buf ); receive ( data_buf ); receive ( data_buf ); receive ( data_buf ); end endmodule 查看测试结果: 图 12 黑金实验多字节收发逻辑图 分析黑金的代码与特权的代码的细微差别! 黑金文档中对其代码时序有这样一个时序图: 其根据时序的理解和逻辑操作十分严格,整个发送和读取均消耗了 11 个通讯时钟周期,而且在最后一个时钟操作和读取完成之后,还在继续计数延时,直至整个时序严格完整的执行完毕。读取和发送的速率是完全相同的,但在连续字节发送中中间没有充足的间隔时间让控制器反应启动数据的发送,导致部分数据没有发送数据,但其已经接收到数据。 图 13 黑金连续多字节连续发送逻辑分析 而对于特权的代码为什么可以完成这样的实验呢? 在特权的代码中在最后一个数据的读取或者是发送完成之后,其并没有等待半个周期而是立即结束了发送和接收,这个半个发送周期对于 50Mhz 的时钟来说,有(波特率为 115200 ) 217 个时钟脉冲,足以让其启动发送命令。 解决黑金的代码完成多字节传输方法: 1 、如黑金所说,添加缓冲器 2 、提高控制模块时钟 3 、修改驱动最后一步,提前结束时序传输。 实验十五 FIFO 存储模块(同步) 该实验自己设计了一个 FIFO 模块,作为收发串口模块之间的缓冲器,可完成多字节的收发功能。 2.3 开发环境 本次设计采用 Altera 公司提供的 FPGA 开发环境 QuartusII14.1 ,并安装 modelsim-Altera 作为 FPGA 开发过程中的逻辑仿真工具。 2.4 设计思路 2.4.1 串口发送实验 串口发送部分驱动代码,在功能上预设分为两部分: 1. 波特率发生器 ; 2 串口输出总线控制模块。 波特率发生器部分为一个同步使能脉冲发生器,在同步使能控制下产生一定频率的脉冲信号供串口输出总线进行数据的节拍。 串口输出总线控制模块采用一个有限状态机的的方式,状态转移图如图 1 图 14 状态转移图 图 1 所示为串口的发送状态机状态转移图,图中第二位到第七位的数据发送状态以省略号替代。 设定状态寄存器 i ,初值为 0 ,则 i=0 :检测启动信号是否为高电平,启动信号被置高,立即开始发送数据进入下个状态; i=1 :将波特率发生器同步使能信号拉高,进入下个状态; i=2 : 等待波特率发生器的脉冲信号,以其脉冲为发送时钟发送起始位,进入下个状态 ; i=3 、 4 、 5 、 6 ······、 10 :等待波特率发生器的脉冲信号,以其脉冲为发送时钟发送数据 0 位、 1 位、 2 位、 3 位······、 7 位,进入下个状态; i=11 :等待波特率发生器的脉冲信号,以其脉冲为发送时钟发送停止位,进入下个状态; i=12 :发送结束,拉高结束信号 done 并拉低波特率发生器同步使能信号停止产生波特率,进入下个状态; i=13 :拉低结束信号 done ,进入下个状态; i=14 :返回检测启动信号状态,进入 i=0 状态; 串口发送接口定义,如表 1 : 接口名称 位宽 功能说明 C lk 1 提供串口工作时钟 R st_n 1 异步复位信号,低电平有效 Start 1 发送数据启动信号,高电平有效 Sdata 8 发送数据并行输入接口 Tx 1 串口输出线 D one 1 发送完成反馈信号线 2.4.2 串口接收实验 串口接收驱动代码的编写,笔者认为思路上与串口的发送并不相同,即使两者使用着同一种通信协议。对于串口发送驱动,由于是发送数据方,发送时钟的节拍可根据协议十分清楚的产生,而对于串口数据的接收,驱动必须产生更为合适的时钟进行采样才能保证数据的稳定性。对于串口的接收驱动,编程采用完全被动模式,即驱动随时等候响应外部起始信号进行串口数据接收。 串口接收驱动,在功能上可分为两部分: 1. 波特率发生器; 2 串口接收模块。在波特率发生器部分与串口的发送机制相同,为一个简单的同步时钟脉冲发生器。 所说串口接收驱动的波特率发生器与发送驱动完全相同,但在状态机如何利用其脉冲节拍上却稍有不同,具体可由本文第三章进行细节描述。串口接收驱动主题思想依然是个有限状态机,状态转移图如图 2 所示: 图 15 串口接收驱动状态转移图 设定状态寄存器 i ,初值为 0 ,则 i=0 : 检测串口总线上的电平 ,检测到低电平立即开始数据的接收; i=1 :将波特率发生器同步使能信号拉高,进入下个状态; i=2 :与发送状态机不同的是,当波特率发生器被使能后的第一个时钟脉冲需要被跳过,检测到第一个时钟脉冲不做任何处理,进入下个状态; i=3 、 4 、 5 、 6 ······、 10 :等待波特率发生器的脉冲信号,接收 RX 总线上的数据 0 位、 1 位、 2 位、 3 位······、 7 位存储到 buffer 各位中,进入下个状态; i=11 :等待波特率发生器的脉冲信号,缓存此时 RX 电平信号,进入下个状态; i=12 :校验停止位缓存是否为逻辑 1 ,否则跳入 i=0 全部此次接收失败,校验正确则进入下个状态; i=13 :发送结束,拉高结束信号 done 并拉低波特率发生器同步使能信号停止产生波特率,进入下个状态; i=14 :拉低结束信号 done ,进入下个状态; i=15 :返回检测串口总线上的电平状态,进入 i=0 状态; 串口接收接口定义: 接口名称 位宽 功能说明 C lk 1 提供串口工作时钟 R st_n 1 异步复位信号,低电平有效 Rdata 8 接收数据并行输出接口 Rx 1 串口输入线 D one 1 接收完成反馈信号线 相较于串口的发送接口,串口的接收更为独立,完全有外部串口数据决定,只有一有数据就可以立刻响应,不需要接收 FPGA 端控制,接收完成后产生一个结束信号,提醒 FPGA 提取数据即可。 2.4.3 串口收发( FIFO )实验 本实验重点在如何控制 FIFO 存储器对串口收发驱动进行缓冲。整个实验的模块构架框图如图所示,串口驱动采用前两个实验驱动代码, FIFO 调用 QuartusII 内部 IP 核 FIFO 存储器,采用状态机控制串口接收数据存储在 FIFO 中,核心模块只要 FIFO 中有数据就立刻取数据读出通过串口传输出去。 图 16 模块构架框图 FIFO 存储控制状态机: 图 17FIFO 存储状态机 FIFO 存储状态机功能简单,要串口接收到数据,立刻响应读取数据存入 FIFO 中。 核心模块状态机: 图 18 核心模块状态机状态转移图 3 设计的实现 3.1 串口发送实验 串口的发送驱动基本采用特权同学的代码思想,控制信号的外部建模方式采用黑金的建模方式。发送驱动代码: module UART_send ( clk , rst_n , TX , Sdata , done , Start ); parameter clk_frq = 50_000_000 , UART_bsp = 115200 ; input clk ; input rst_n ; input Start ; input Sdata ; output done ; output TX ; reg UART_plus ; reg cnt ; reg i ; reg done ; reg TX ; reg enable ; reg Sdatabuf ; //FSM always @( posedge clk , negedge rst_n ) begin if ( ! rst_n ) begin i = 4'd0 ; done = 1'b0 ; TX = 1'b1 ; enable = 1'b0 ; Sdatabuf = 8'h00 ; end else begin case ( i ) 4'd0 : if ( Start ) begin i = i + 1'b1 ; Sdatabuf = Sdata ; end 4'd1 : begin i = i + 1'b1 ; enable = 1'b1 ; end 4'd2 : if ( UART_plus ) begin i = i + 1'b1 ; TX = 1'b0 ; end 4'd3 : if ( UART_plus ) begin i = i + 1'b1 ; TX = Sdatabuf ; end 4'd4 : if ( UART_plus ) begin i = i + 1'b1 ; TX = Sdatabuf ; end 4'd5 : if ( UART_plus ) begin i = i + 1'b1 ; TX = Sdatabuf ; end 4'd6 : if ( UART_plus ) begin i = i + 1'b1 ; TX = Sdatabuf ; end 4'd7 : if ( UART_plus ) begin i = i + 1'b1 ; TX = Sdatabuf ; end 4'd8 : if ( UART_plus ) begin i = i + 1'b1 ; TX = Sdatabuf ; end 4'd9 : if ( UART_plus ) begin i = i + 1'b1 ; TX = Sdatabuf ; end 4'd10 : if ( UART_plus ) begin i = i + 1'b1 ; TX = Sdatabuf ; end 4'd11 : if ( UART_plus ) begin i = i + 1'b1 ; TX = 1'b1 ; end 4'd12 : begin i = i + 1'b1 ; done = 1'b1 ; enable = 1'b0 ; end 4'd13 : begin i = i + 1'b1 ; done = 1'b0 ; end 4'd14 : i = 4'd0 ; default : i = 4'd0 ; endcase end end //generateclock always @( posedge clk , negedge rst_n ) begin if ( ! rst_n ) begin UART_plus = 1'b0 ; cnt = 32'd0 ; end else begin if ( enable ) begin if ( cnt == clk_frq / UART_bsp ) cnt = 32'd0 ; else if ( cnt == clk_frq / UART_bsp / 2 ) begin UART_plus = 1'b1 ; cnt = cnt + 1'd1 ; end else begin cnt = cnt + 1'd1 ; UART_plus = 1'b0 ; end end else begin cnt = 32'd0 ; UART_plus = 1'b0 ; end end end endmodule 外部控制模块,状态机编程: module UARTop ( clk , rst_n , TX ); input clk ; input rst_n ; output TX ; reg Start ; reg Sdata ; UART_sendUART_send ( . clk ( clk ), . rst_n ( rst_n ), . TX ( TX ), . Sdata ( Sdata ), . done ( done ), . Start ( Start ) ); //FSM reg i ; always @( posedge clk , negedge rst_n ) begin if ( ! rst_n ) begin Sdata = 8'd0 ; Start = 1'b0 ; i = 5'd0 ; end else begin case ( i ) 5'd0 : begin i = i + 1'b1 ; Sdata = 8'haa ; end 5'd1 : begin i = i + 1'b1 ; Start = 1'b1 ; end 5'd2 : begin i = i + 1'b1 ; Start = 1'b0 ; end 5'd3 : if ( done == 1'b1 ) begin i = i + 1'b1 ; end 5'd4 : begin i = i + 1'b1 ; Sdata = 8'hf0 ; end 5'd5 : begin i = i + 1'b1 ; Start = 1'b1 ; end 5'd6 : begin i = i + 1'b1 ; Start = 1'b0 ; end 5'd7 : if ( done == 1'b1 ) begin i = i + 1'b1 ; end 5'd8 : begin i = i + 1'b1 ; Sdata = 8'h0f ; end 5'd9 : begin i = i + 1'b1 ; Start = 1'b1 ; end 5'd10 : begin i = i + 1'b1 ; Start = 1'b0 ; end 5'd11 : if ( done == 1'b1 ) begin i = i + 1'b1 ; end 5'd12 : begin i = i + 1'b1 ; Sdata = 8'h55 ; end 5'd13 : begin i = i + 1'b1 ; Start = 1'b1 ; end 5'd14 : begin i = i + 1'b1 ; Start = 1'b0 ; end 5'd15 : if ( done == 1'b1 ) begin i = i + 1'b1 ; end 5'd16 : i = 5'd16 ; default :; endcase end end endmodule 其功能是发送连续 8'haa 、 8'hf0 、 8'h0f 、 8'h55 四个字节数据。而后状态机将滞留在 i=16 状态,整个工作停止。 testbench 文件,只需提供时钟与复位信号: `timescale 1ns / 1ns module testbench ; reg clk ; reg rst_n ; wire TX ; initial begin clk = 1'b1 ; rst_n = 1'b0 ; # 200 rst_n = 1'b1 ; # 400000 $stop ; end always # 10 clk = ~ clk ; UARTopUARTop ( . clk ( clk ), . rst_n ( rst_n ), . TX ( TX ) ); endmodule modelsim 测试逻辑图: 图 19modelsim 测试逻辑图 3.2 串口接收实验 串口接收设计备用完全独立试的设计,有考虑到串口的接收时异步模式,外部随时可能有信号传入,串口只要在非接收状态,都可以随时响应起始信号,开始接收数据,不受外部使能信号控制,接收完成反馈接收结束信号。 建模图: 图 20 串口接收模型 module UARTreceive ( clk , rst_n , RX , Rdata , done ); parameter clk_frq = 50_000_000 , bsp = 115200 ; input clk ; input rst_n ; input RX ; output Rdata ; output done ; reg i ; reg Rclk_pulse ; reg cnt ; reg Rdatabuf ; reg stopbuf ; reg enable ; reg Rdata ; reg done ; //FSM always @( posedge clk , negedge rst_n ) begin if ( ! rst_n ) begin i = 4'd0 ; stopbuf = 1'b0 ; Rdatabuf = 8'h00 ; Rdata = 8'h00 ; enable = 1'b0 ; done = 1'b0 ; end else begin case ( i ) 4'd0 : if ( RX == 1'b0 ) i = i + 1'b1 ; 4'd1 : begin i = i + 1'b1 ; enable = 1'b1 ; end 4'd2 : if ( Rclk_pulse ) i = i + 1'b1 ; 4'd3 : if ( Rclk_pulse ) begin i = i + 1'b1 ; Rdatabuf = RX ; end 4'd4 : if ( Rclk_pulse ) begin i = i + 1'b1 ; Rdatabuf = RX ; end 4'd5 : if ( Rclk_pulse ) begin i = i + 1'b1 ; Rdatabuf = RX ; end 4'd6 : if ( Rclk_pulse ) begin i = i + 1'b1 ; Rdatabuf = RX ; end 4'd7 : if ( Rclk_pulse ) begin i = i + 1'b1 ; Rdatabuf = RX ; end 4'd8 : if ( Rclk_pulse ) begin i = i + 1'b1 ; Rdatabuf = RX ; end 4'd9 : if ( Rclk_pulse ) begin i = i + 1'b1 ; Rdatabuf = RX ; end 4'd10 : if ( Rclk_pulse ) begin i = i + 1'b1 ; Rdatabuf = RX ; end 4'd11 : if ( Rclk_pulse ) begin i = i + 1'b1 ; stopbuf = RX ; end 4'd12 : if ( stopbuf == 1 ) begin i = i + 1'b1 ; stopbuf = 1'b0 ; end else i = 4'd0 ; 4'd13 : begin i = i + 1'b1 ; Rdata = Rdatabuf ; enable = 1'b0 ; end 4'd14 : begin i = i + 1'b1 ; done = 1'b1 ; end 4'd15 : begin i = i + 1'b1 ; done = 1'b0 ; end default : i = 4'd0 ; endcase end end //generateUARTclock always @( posedge clk , negedge rst_n ) begin if ( ! rst_n ) begin Rclk_pulse = 1'b0 ; cnt = 32'd0 ; end else begin if ( enable ) if ( cnt == clk_frq / bsp ) cnt = 32'd0 ; else if ( cnt == clk_frq / bsp / 2 ) begin cnt = cnt + 1'b1 ; Rclk_pulse = 1'b1 ; end else begin cnt = cnt + 1'b1 ; Rclk_pulse = 1'b0 ; end else cnt = 32'd0 ; end end endmodule 串口接收需要外部输入信号,该实验采用了上个实验验证正确的串口发送代码来作为串口接收的信号来源,也可编写串口模型测试。由于笔者测试过串口接收逻辑正确,直接进行顶层连线,完成串口的收发实验目的。 顶层控制模块: module UARTTop ( clk , rst_n , RX , TX ); input clk ; input rst_n ; input RX ; output TX ; wire Rdone , Tdone ; wire Rdata ; reg Sdata ; reg Start ; UART_sendUART_send ( . clk ( clk ), . rst_n ( rst_n ), . TX ( TX ), . Sdata ( Sdata ), . done ( Tdone ), . Start ( Start ) ); UARTreceiveUARTreceive ( . clk ( clk ), . rst_n ( rst_n ), . RX ( RX ), . Rdata ( Rdata ), . done ( Rdone ) ); //FSM reg transfer_i ; always @( posedge clk , negedge rst_n ) begin if ( ! rst_n ) begin transfer_i = 4'd0 ; Start = 1'd0 ; Sdata = 8'hf0 ; end else begin case ( transfer_i ) 4'd0 : begin transfer_i = transfer_i + 1'b1 ; Start = 1'b1 ; end 4'd1 : begin transfer_i = transfer_i + 1'b1 ; Start = 1'b0 ; end 4'd2 : if ( Tdone ) transfer_i = transfer_i + 1'b1 ; 4'd3 : if ( Rdone ) begin transfer_i = transfer_i + 1'b1 ; Sdata = Rdata ; end 4'd4 : begin transfer_i = transfer_i + 1'b1 ; Start = 1'b1 ; end 4'd5 : begin transfer_i = transfer_i + 1'b1 ; Start = 1'b0 ; end 4'd6 : if ( Tdone ) transfer_i = transfer_i + 1'b1 ; 4'd7 : transfer_i = 4'd3 ; default : transfer_i = 4'd3 ; endcase end end endmodule 3.3 串口多字节接收实验( FIFO ) 首先整合前两个实验的串口收发代码,例化为一个模块 UART_driver : 图 21UART_driverRTL 视图 端口说明: 端口名称 属性(位宽) 描述 clk, input ( 1 ) 工作时钟 rst_n, input ( 1 ) 异步复位信号,低电平有效 TX_data, input ( 8 ) 传输数据 8 为并口输入 TX_request, input ( 1 ) 发送请求信号,电平启动发送 TX_done, output ( 1 ) 发送完成信号 TX, output ( 1 ) 串口输出端口 RX_data, output ( 8 ) 接收数据 8 为并口输出 RX_EN, input ( 1 ) 接收使能信号 RX_done, output ( 1 ) 接收结束信号 RX input ( 1 ) 串口接收端口 例化出串口模块之后,开始生成 FIFO 模块。调用 QuartusII 内部集成的 FIFO 存储器 IP 核: 图 22QuartusIIFIFOIP 核调用界面 IP 核调用使用同源读写时钟, FIFO 的位宽为 8 位栈深度为 4 个字节,添加一个异步清零端口,其余默认设置: 图 23FIFO 设置界面 图 24FIFO 设置界面 FIFO 端口说明: 端口名称 属性(位宽) 描述 aclr input ( 1 ) 异步清零位,高电平清楚 FIFO 内部数据 clock input ( 1 ) FIFO 数据时钟,上升沿锁存端口信息 data input ( 8 ) 8 为并口存储数据位,在 wrreq =1 时,时钟上升沿存储数据 rdreq input ( 1 ) 读取请求,高电平在时钟上升沿有效 wrreq input ( 1 ) 写入请求,高电平在时钟上升沿有效 empty output ( 1 ) FIFO 空栈状态标志位 full output ( 1 ) FIFO 满栈态标志位 q output ( 8 ) 读取数据 8 位数据输出端口 usedw output ( 2 ) FIFO 内部数据数 调用官方 IP 核不需要了解其内部逻辑,直接进行功能测试,先观察各个接口功能时序功能,使用 modelsim 测试时序图: 图 25FIFO 逻辑功能测试 完成了 FIFO 的功能测试,编写顶层控制模块以及由收数据到 FIFO 的存储控制: module UART_fifotop ( clk , rst_n , RX , TX ); input clk ; input rst_n ; input RX ; output TX ; //--------------------------------------------------------------- reg WR_data ; reg WR_request ; wire RD_data ; reg RD_request ; wire empty ; wire full ; wire usedw_sig ; //--------------------------------------------------------------- reg TX_data ; reg TX_request ; wire TX_done ; wire RX_data ; reg RX_EN ; wire RX_done ; //--------------------------------------------------------------- reg uart2fifo_i ; reg i ; //--------------------------------------------------------------- //UARTdrivermodule //--------------------------------------------------------------- UART_driverUART_driver ( . clk ( clk ), . rst_n ( rst_n ), . TX_data ( TX_data ), . TX_request ( TX_request ), . TX_done ( TX_done ), . TX ( TX ), . RX_data ( RX_data ), . RX_EN ( RX_EN ), . RX_done ( RX_done ), . RX ( RX ) ); //--------------------------------------------------------------- //FIFOmodule //--------------------------------------------------------------- fifofifo_inst ( . aclr ( ~ rst_n ), . clock ( clk ), . data ( WR_data ), . rdreq ( RD_request ), . wrreq ( WR_request ), . empty ( empty ), . full ( full ), . q ( RD_data ), . usedw ( usedw_sig ) ); //--------------------------------------------------------------- always @( negedge clk , negedge rst_n ) begin if (! rst_n ) begin uart2fifo_i = 4'd0 ; RX_EN = 1'b1 ; WR_data = 8'h00 ; WR_request = 1'b0 ; end else begin case ( uart2fifo_i ) 4'd0 : if ( RX_done ) begin uart2fifo_i = uart2fifo_i + 1'b1 ; WR_data = RX_data ; end 4'd1 : if ( full == 1'b0 ) begin uart2fifo_i = uart2fifo_i + 1'b1 ; WR_request = 1'b1 ; end else uart2fifo_i = 4'd1 ; 4'd2 : begin uart2fifo_i = uart2fifo_i + 1'b1 ; WR_request = 1'b0 ; end 4'd3 : uart2fifo_i = 4'd0 ; default : uart2fifo_i = 4'd0 ; endcase end end always @( negedge clk , negedge rst_n ) begin if (! rst_n ) begin i = 5'd0 ; RD_request = 1'b0 ; TX_data = 8'h00 ; TX_request = 1'b0 ; end else begin case ( i ) 5'd0 : if ( empty == 1'b0 ) begin i = i + 1'b1 ; RD_request = 1'b1 ; end 5'd1 : begin i = i + 1'b1 ; RD_request = 1'b0 ; TX_data = RD_data ; end 5'd2 : begin i = i + 1'b1 ; TX_request = 1'b1 ; end 5'd3 : begin i = i + 1'b1 ; TX_request = 1'b0 ; end 5'd4 : if ( TX_done ) i = 1'b0 ; endcase end end endmodule 在测试的时候调用在,黑金实验中编写的多字节发送的串口模型做为测试文件,在 testbench 中只提供了时钟信号,仿真逻辑图如下图 26 : 图 26 串口多字节收发 FIFO 缓存逻辑仿真 4 设计的验证 4.1 串口发送板级测试结果 由于状态机发送 4 个数据后停止工作,故板级测试时,需按下复位键后,打开串口,松开复位键后观察串口助手,结果如图: 4.2 串口接收板级测试结果 测试串口发送任何数据, FPGA 接收后立刻返回原有数据,测试结果如图: 4.3 串口多字节接收 FIFO 板级测试结果 5 设计总结 5.1 思考问题 本次实验对 FPGA 的控制语言有了一定练习,同时也产生了许多疑惑: 1 、在 FPGA 编写底层代码之后,在顶层调用时,采用状态机的方式,在时钟上许多多要工作在统一时钟的环境下,控制模块的时钟过快,有时的触发响应信号底层可能无法反应,过慢又有可能错过底层驱动的反馈信号。当在目前逻辑比较少的时候可能时钟控制还比较简单,都是用一个时钟也没什么致命的缺陷。但在以后的复杂设计中,这时钟的管理分析应如何解决? 2 、写一些总线驱动的目的在于以后的方便使用,而这些驱动的控制接口有没有一种固定的格式或者规范? 5.2 编程过程报错汇总 1 Error(10028):Can'tresolvemultipleconstantdriversfornet"Rdatabuf "atUARTreceive.v(41) 一个变量无法在多个 always 块语句中进行操作 2 BEGIN-ENDrequiredaroundtask/functionstatements task 与 function 语法中,功能语句必须为一个块语句(前后用 begin......end ) 3 Error(10663):VerilogHDLPortConnectionerroratUART_fifotop.v(31):outputorinoutport"TX"mustbeconnectedtoastructuralnetexpression 输出必须连接 wire 变量,连接 reg 型报错。
个人分类: FPGA学记|5565 次阅读|0 个评论
分享 参与HELPER2416开发板助学计划:USB转串口在win8.1安装不成功的解决方法
moonyuan 2014-7-6 16:42
参与HELPER2416开发板助学计划 -今天用 君益兴 提供的 USB转串口工具 和光盘镜像里提供的驱动 在电脑上装了一下。居然不行,有 叹号。 -系统是 win 8.1 64bit 企业版 -问了一下百度,有的说驱动不对,有的说PL2303官方有说明 不支持 win 8.1,有的说要关闭驱动自动更新 等等。 -搞了一下早上,发现换一个驱动即可。附上驱动。 PL-2303 Win7(X32)(X64) Driver Installer.rar (1.62 MB, 下载次数: 0) -安装之后,设备管理器 - prolific USB-to-Serial Comm Port - 更新驱动程序软件 - 出现如下图 -选择 浏览计算机以查找驱动程序软件 - 从计算机的设备驱动程序列表中选取 -选择 版本:3.2.0.0 - 下一步 -问题解决了,叹号不见了。 -希望对遇到同样问题的童鞋有帮助 论坛ID:moonyuan 提交时间:2014.07.06 本文来自论坛,点击查看完整帖子内容。
个人分类: Helper2416|437 次阅读|0 个评论
分享 MDKA5D3X-EK_T70开发板IAR EWARM裸机例程05:usart_serial(串口例子)
mcuzone1 2014-2-23 21:26
By Mcuzone 这个例子演示了由USART外设提供的正常(串行UART)模式。 使用2个串口分别连接调试串口和USART1,程序运行后,调试串口输出调试信息,USART1被配置ECHO模式,自动将串口接收到的数据回发。 硬件平台:MDKA5D3X-EK_T70开发板 开发板所带操作系统:linux 3.6.9 裸机程序编译工具:IAR Embedded Workbench for ARM 6.60 裸机程序调试工具:J-LINK 给开发板上电,启动开发板的linux操作系统,连续按下PC端键盘的空格键,使开发板停在U-Boot下,然后运行本例子 调试串口输出调试信息 USART1将收到的信息回发
个人分类: A5D3示例|152 次阅读|0 个评论
分享 【K70EK_T7_MQX例程】010串口打印Hello和world(hello2)
mcuzone1 2014-1-31 22:44
By Mcuzone 调试串口波特率:115200-uart2 基于MDKK70-EK_T7硬件平台 分别打印hello和world,由于world有自动启动属性,先执行,在world函数中创建了hello任务打印hello,再执行打印world. #define HELLO_TASK5 #define WORLD_TASK6 extern void hello_task(uint_32); extern void world_task(uint_32); const TASK_TEMPLATE_STRUCTMQX_template_list[] = { /* Task Index, Function, Stack,Priority, Name, Attributes,Param, Time Slice */ { WORLD_TASK, world_task, 1000, 9,"world",MQX_AUTO_START_TASK, 0, 0 }, { HELLO_TASK, hello_task, 1000, 8,"hello",0, 0, 0 }, { 0 } }; /*TASK*----------------------------------------------------- * * Task Name: world_task * Comments : *This task creates hello_task and then prints " World ". * *END*-----------------------------------------------------*/ void world_task ( uint_32 initial_data ) { _task_id hello_task_id; hello_task_id = _task_create(0, HELLO_TASK, 0); if (hello_task_id == MQX_NULL_TASK_ID) { printf ("\n Could not create hello_task\n"); } else { printf(" World \n"); } _task_block(); } /*TASK*----------------------------------------------------- * * Task Name: hello_task * Comments : *This task prints " Hello". * *END*-----------------------------------------------------*/ void hello_task ( uint_32 initial_data ) { printf("\n Hello\n"); _task_block(); } 串口输出:
个人分类: K70示例|107 次阅读|0 个评论
分享 【K70EK_T7_MQX例程】009串口打印Helloworld(hello)
mcuzone1 2014-1-31 22:39
By Mcuzone 调试串口波特率:115200-uart2 基于MDKK70-EK_T7硬件平台 关键词: Freescale 飞思卡尔 Kinetis MQX Cortex-M4 K70 MDKK70 核心板 开发板 例程 demo sci uart 程序很简单,就是一个打印HelloWorld的例子,可作为模板 /* Task IDs */ #define HELLO_TASK 5 extern void hello_task(uint_32); const TASK_TEMPLATE_STRUCTMQX_template_list[] = { /* Task Index, Function, Stack,Priority, Name, Attributes,Param, Time Slice */ { HELLO_TASK, hello_task, 1500, 8,"hello",MQX_AUTO_START_TASK, 0, 0 }, { 0 } }; /*TASK*----------------------------------------------------- * * Task Name: hello_task * Comments : *This task prints " Hello World " * *END*-----------------------------------------------------*/ void hello_task ( uint_32 initial_data ) { printf("Hello World\n"); _task_block(); } 串口输出:
个人分类: K70示例|141 次阅读|0 个评论
分享 【K70例程】002串口输出CPU信息,串口输入中断
mcuzone1 2014-1-11 13:15
By Mcuzone 使用J-LinkV8 MDKKeil版本4.71 调试串口波特率:115200 基于MDKK70-EK_T43硬件平台 实验现象: 串口输出: Mcuzone_K70 FW_Version:100 External Pin Reset Family:K70 PinCnt:256 SiliconRevID:1.0 PFlash Size: 1024KB FlexNVM Size: 0KB RAM Size :128KB CoreClock: 120000000Hz BusClock: 60000000Hz FlexBusClock: 30000000Hz FlashClock: 20000000Hz 用户按键盘输入,K70返回输入的字符到超级终端显示 例程及示例: 链接: http://pan.baidu.com/s/1qW54ZTU 密码: lrlq
个人分类: K70示例|183 次阅读|0 个评论

Archiver|手机版|小黑屋|电子工程世界 ( 京ICP证 060456 )

GMT+8, 2020-4-5 17:18 , Processed in 0.302628 second(s), 15 queries , Gzip On, MemCache On.

Powered by EEWORLD电子工程世界

© 2020 http://bbs.eeworld.com.cn/

返回顶部