工作笔记——CPLD与MCU通过SPI通信
一、需求描述
- MCU需要接收来自CPLD的升级固件数据
- CPLD对MCU只进行发送数据,不接收MCU的数据
- CPLD无法告知数据传输的开始和结束,需要MCU自行判断(CPLD只是数据透传,不做数据判断)
- 数据通信速率至少是UART通信的115200波特率
- PCB上MCU与CPLD之间通过3个普通IO引脚连接
二、功能分析
MCU与CPLD之间有3根线,那么可以选择UART通信或者SPI通信方式。
由于CPLD无法通知MCU数据传输的开始与结束,MCU需要自行判别,那么MCU可以通过中断方式来检测数据传输的开始,通过超时来检测数据传输的结束。
UART与SPI的区别在于前者是异步通信后者是同步通信方式,不论是SPI还是UART方式都需要MCU通过IO模拟方式软件实现。使用UART传输如果收发双方产生的波特率存在偏差则会导致数据传输出错,而同步传输方式有时钟信号的约束,相比异步传输方式数据准确率会更高。如果使用软件模拟UART,需要使用定时器作为波特率发生器。如果波特率比较高,那么定时器中断频率就需要更高,这样会影响整个MCU系统的实时性。综合考虑后选择SPI方式。
CPLD对MCU只发送数据,那么MCU只需要作为SPI的从机即可,三个IO分配为SPI的CS、CLK、DAT引脚。
由于CS是低电平有效,那么将CS引脚配置为中断输入方式,当CS中断触发后开始数据接收处理。因为CPLD也不知道数据传输什么时候结束,所以无法通过将CS置高电平来告诉数据传输的结束,那么CS置高电平只能表明一个字节传输结束。MCU可以通过超时方式来判断一包数据的结束,类似于串口的空闲中断方式。
SPI数据接收在外部中断中操作。将CLK引脚配置为外部中断的
上升沿触发
,CS有效的情况下CLK中断触发后进行数据接收。SPI空闲中断采用100us周期定时器判断。为了MCU系统的实时性,只有CS中断触发后才会开启定时器,超时判断完成后关闭定时器。
CPLD向MCU发送一字节的时序图如下(速率:200KBit/s):
三、软件实现
GPIO的配置:无数据CLK为低电平,CS低有效。CS上升沿、下降沿都会触发中断,判断1字节传输的起始与结束;CLK上升沿触发中断,数据在CLK上升沿采样
/*
***********************************************************************************************
* 函 数: BSP_CPLD_GPIO_Init
* 描 述: 配置CPLD的SPI通信引脚
* 输 入: 无
* 输 出: 无
***********************************************************************************************
*/
void BSP_CPLD_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
CPLD_PIN_CLK_ENABLE();
/*Configure GPIO pin : PtPin */
GPIO_InitStruct.Pin = CPLD_SPI_CSN_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING_FALLING;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(CPLD_SPI_CSN_PORT, &GPIO_InitStruct);
GPIO_InitStruct.Pin = CPLD_SPI_SCK_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
GPIO_InitStruct.Pull = GPIO_PULLDOWN;
HAL_GPIO_Init(CPLD_SPI_SCK_PORT, &GPIO_InitStruct);
GPIO_InitStruct.Pin = CPLD_SPI_DAT_PIN;
GPIO_InitStruct.Pull = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(CPLD_SPI_DAT_PORT, &GPIO_InitStruct);
}
CPLD_SPI_CS外部中断函数:用于使能数据接收、空闲检测
/*
***********************************************************************************************
* 函 数: CPLD_CS_EXTI_IRQHandler
* 描 述: CPLD_SPI_CS中断函数
* 输 入: 无
* 输 出: 无
***********************************************************************************************
*/
void CPLD_CS_EXTI_IRQHandler(void)
{
if (__HAL_GPIO_EXTI_GET_IT(CPLD_SPI_CSN_PIN))
{
if (READ_BIT(CPLD_SPI_CSN_PORT->IDR, CPLD_SPI_CSN_PIN))
{
/* CS高失能SPI数据接收 */
CLEAR_BIT(g_tCpldSpi.ucState, CPLD_FLAG_CSN);
}
else
{
/* CS低使能SPI数据接收 */
s_tCpldSpi.ucByte = 0;
s_tCpldSpi.ucBitCount = 0;
SET_BIT(g_tCpldSpi.ucState, CPLD_FLAG_CSN);
/* 开启空闲检测 */
if (0 == s_tCpldSpi.ucIdleCheck)
{
s_tCpldSpi.ucIdleCheck = 1;
HAL_TIM_Base_Start_IT(&Tim7Handle);
}
}
__HAL_GPIO_EXTI_CLEAR_IT(CPLD_SPI_CSN_PIN);
}
}
CPLD_SPI_SCK外部中断函数:用于SPI数据的接收
/*
***********************************************************************************************
* 函 数: CPLD_SCK_EXTI_IRQHandler
* 描 述: CPLD_SPI_SCK中断函数
* 输 入: 无
* 输 出: 无
***********************************************************************************************
*/
void CPLD_SCK_EXTI_IRQHandler(void)
{
if (__HAL_GPIO_EXTI_GET_IT(CPLD_SPI_SCK_PIN))
{
/* CSN有效则进行数据接收 */
if (READ_BIT(g_tCpldSpi.ucState, CPLD_FLAG_CSN))
{
if (READ_BIT(CPLD_SPI_DAT_PORT->IDR, CPLD_SPI_DAT_PIN))
{
s_tCpldSpi.ucByte |= (0x80 >> s_tCpldSpi.ucBitCount);
}
else
{
s_tCpldSpi.ucByte &= ~(0x80 >> s_tCpldSpi.ucBitCount);
}
/* 收满一字节后存向接收FIFO */
if (++s_tCpldSpi.ucBitCount > 7)
{
g_tCpldSpi.ucaRxBuf[g_tCpldSpi.usRxWrite] = s_tCpldSpi.ucByte;
if (++g_tCpldSpi.usRxWrite >= 1024)
{
g_tCpldSpi.usRxWrite = 0;
}
if (g_tCpldSpi.usRxCount < CPLD_SPI_RX_BUF_LEN)
{
g_tCpldSpi.usRxCount++;
}
/* SPI收到新数据,设置一个标记,供应用程序查询 */
SET_BIT(g_tCpldSpi.ucState, CPLD_FLAG_RXNE);
}
}
__HAL_GPIO_EXTI_CLEAR_IT(CPLD_SPI_SCK_PIN);
}
}
定时器中断函数:判断CPLD_SPI空闲中断的发生
/*
***********************************************************************************************
* 函 数: TIM7_IRQHandler
* 描 述: 定时器7中断函数,100us中断周期
* 输 入: 无
* 输 出: 无
***********************************************************************************************
*/
void TIM7_IRQHandler(void)
{
static uint16_t t100us_cnt = 0;
if (__HAL_TIM_GET_FLAG(&Tim7Handle, TIM_FLAG_UPDATE) &&
__HAL_TIM_GET_IT_SOURCE(&Tim7Handle, TIM_IT_UPDATE))
{
/* CPLD-SPI空闲检测,1ms */
if (READ_BIT(g_tCpldSpi.ucState, CPLD_FLAG_CSN))
{
t100us_cnt = 0;
}
else
{
t100us_cnt++;
}
if (t100us_cnt > 10)
{
/* SPI收到一帧数据,设置一个标记,供应用程序查询 */
SET_BIT(g_tCpldSpi.ucState, CPLD_FLAG_IDLE);
#if ENABLE_RTOS
tx_event_flags_set(&tx_event_flags, TX_EVENT_CPLD_SPI_IDLE, TX_OR);
#endif
s_tCpldSpi.ucIdleCheck = 0;
HAL_TIM_Base_Stop_IT(&Tim7Handle);
}
__HAL_TIM_CLEAR_IT(&Tim7Handle, TIM_IT_UPDATE);
}
}
四、功能验证
经多次发送固件数据验证,MCU均能正常接收数据,并且没有出现数据错误的情况,可用于该项目。
五、拓展
该方法也可以用于实现模拟UART功能,仅提供思路,未经过验证(以115200,8-N-1
为例)。
- 将UART的接收引脚配置为上拉模式、下降沿触发中断(判断起始位)。
- 中断第一次触发后表明收到起始位,收到起始位后打开定时器中断(如果波特率为115200,那么中断周期应小于8.6us,如果每Bit数据需要多次采样,则需要更短的中断周期)。
- 每中断一次判断一次数据引脚,数据收满10Bit后判断是否为停止位,若数据接收正确则存入接收FIFO。
- 在收到起始位后开始计时,1ms内没有再次收到起始位则认为收到一帧数据,产生软空闲中断,然后关闭定时器。
使用该UART方式优势在于比SPI方式使用更少的引脚,只需要1个IO即可完成通信。缺点在于如果要求通信速率高或需要多次采样,那么产生波特率的定时器中断频率高,如果被其他更高优先级中断打断可能造成波特率不准,数据错误。还有就是UART方式在数据通信速率上没有SPI有优势。不到万不得已不建议使用软件UART方式。
工作笔记——CPLD与MCU通过SPI通信的更多相关文章
- SPI通信
SPI是由Motorola公司提出的一种同步串行外围接口:它在速度要求不高,低功耗,需要保存少量参数的智能化传感系统中得到了广泛应用: SPI是一个全双工的同步串行接口,在数据传输过程中,总线上只能是 ...
- 关于SPI通信原理与程序实现
第一次接触SPI是因为当时用到NRF24L01,需要用SPI进行通信.因为2401上面写着MOSI.MISO.SS.RST,当时以为只要用到SPI就肯定有这几个引脚,以至于限制了自己的思维.只认识MI ...
- [转]SPI通信原理简介
[转自]http://www.cnblogs.com/deng-tao/p/6004280.html 1.前言 SPI是串行外设接口(Serial Peripheral Interface)的缩写.是 ...
- ESP8266开发之旅 基础篇⑤ ESP8266 SPI通信和I2C通信
设备与设备之间的通信往往都伴随着总线的使用,而用得比较多的就当属于SPI总线和I2C总线,而恰巧NodeMcu也支持这两种总线通信,所以本章的主要内容就是讲解ESP8266 SPI和I2C总线 ...
- ESP8266 SPI通信
设备与设备之间的通信往往都伴随着总线的使用,而用得比较多的就当属于SPI总线和I2C总线,而恰巧NodeMcu也支持这两种总线通信 1. SPI总线——SPI类库的使用 SPI是串行外设接口(Seri ...
- 理解一下单片机的I2C和SPI通信
应某位网友要求,今天说一下单片机的I2C SPI通信,可能说不清楚,因为这毕竟要做实验才可完全理解. I2C和SPI是两种不同的通信协议. 听到协议,似乎高不可攀,其实协议就是人们定义的一个标准而已, ...
- 2016年第2周读书笔记与工作笔记 scrollIntoView()与datalist元素
这一周主要是看了html5网页开发实例与javascript 高级程序设计,供以后翻阅查找. html5网页开发实例第1章与第二章的2.1部分: 第1章内容: html5在w3c的发展史. 浏览器的 ...
- SPI通信实验---verilog(FPGA作为从机,使用可读可写)
本实验讲究实用性,故设计思想为:主机先向从机发送地址,若是向从机写入数据,则向从机发送数据,若是读取从机数据,则向从机发送时钟,然后在时钟下降沿读取数据即可.cs信号上升沿作为SPI通信的结束信号.r ...
- javascript - 工作笔记 (事件四)
在javascript - 工作笔记 (事件绑定二)篇中,我将事件的方法做了简单的包装, JavaScript Code 12345 yx.bind(item, "click&quo ...
随机推荐
- 动态导航栏和JavaScript箭头函数
动态导航栏和JavaScript箭头函数 今天我们来写一下动态的导航栏,并且学一下JavaScript的箭头函数等相关问题. 样式如下所示: html中执行代码如下所示: <!DOCTYPE h ...
- Git本地仓库和远程仓库冲突解决
场景描述: 在本地创建了一个git repo,并且执行了,git init命令,创建了.gitignore文件,或者README.md文件: 在远程创建了一个git repo,创建时也初始化了.git ...
- ubuntu16.04搭建vulhub环境
简介 Vulhub官方中文教程https://github.com/vulhub/vulhub/blob/master/README.zh-cn.md 环境:ubuntu16.04.5 python3 ...
- springboot中使用Filter、Interceptor和aop拦截REST服务
在springboot中使用rest服务时,往往需要对controller层的请求进行拦截或者获取请求数据和返回数据,就需要过滤器.拦截器或者切片. 过滤器(Filter):对HttpServletR ...
- linux中的fork炸弹
学习bash脚本看到一段代码(老鸟应该知道)挺有意思,一时看不懂.该命令不需要管理员即可运行,请不要在你的机器上使用以下脚本,否则你知道你在干什么 :() { :|: & };: 参考链接:h ...
- Mac插件太多太乱怎么办?CleanMyMac直接帮你搞定!
电脑应用插件在一定程度上便利了大家的生活,保障了用户的使用安全,比如Flash插件.浏览器翻译插件.银行安全登录插件等等.但是许多的插件并不能定位安装的位置,同时部分插件,大部分时候都是只使用一次的, ...
- 通过jquery 获取下拉列表中选中的值对应的value
<div class="col-sm-9"> <select id="device-type" class="form-contro ...
- vue 2.9.6升级到3X版本
先通过 npm uninstall vue-cli -g 卸载vue,然后再安装,但是vue -V时依然是2.9.6版本: 第一步: npm config get registry 第二步: npm ...
- JDBC事务提交机制以及解决方案
JDBC中的事务是自动提交的,什么是自动提交? 只要任意执行一条DML语句,则自动提交一次.这是JDBC默认的事务行为.但是实际业务当中,通常都是N条DML语句共同联合才能完成的,必须保证它们这些DM ...
- Mac 安装Homebrew慢的问题解决
一开始安装,在官网上的命令: /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/ma ...