使用stm32f407,采用ssi方式读取mt6701编码器数据,这是一个14bit的编码器,spi采用仅读取主模式,调试过程中遇到以下问题,在此记录解决过程如下:
Spi
配置:
模式选择
Spi配置:时钟和边沿
常规方式测试
while (1)
{
g_6701_cnt++;
GPIOB->ODR&= (~(1<<15));
HAL_SPI_Receive(&hspi2,g_mt6701_rx_buf, 2, 100);
GPIOB->ODR|= (1<<15);
g_debug_buf[0]= g_stc_svpwm_info_dq.ta;
g_debug_buf[1]= g_stc_svpwm_info_dq.tb;
g_debug_buf[2]= g_stc_svpwm_info_dq.tc;
g_debug_buf[3]= (real_t)(((uint16_t)g_mt6701_rx_buf[0] +(uint16_t)(g_mt6701_rx_buf[1]<<8)) >> 2);
fun_debug_commix_send_dis(g_debug_buf);
HAL_Delay(1);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
测试结果
DMA方式读取
Ioc文件配置
voidfun_set_mt6701_read_start(p_stc_mt6701_info pv)
{
uint16_ttx_data = 0xffff;
GPIOB->ODR&= (~(1<<15));
pv->sta= e_mt6701_busy;
HAL_SPI_Receive_DMA(&hspi2,(uint8_t*)pv->rx_data_buf, 2);
__HAL_DMA_DISABLE_IT(&hdma_spi2_rx,DMA_IT_HT);//取消半传输完成中断
}
voidHAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)
{
if(SPI2== hspi->Instance)
{
fun_mt6701_dma_cb();
}
}
void fun_mt6701_dma_cb(void)
{
uint16_ttmp = 0;
g_cb_cnt++;
GPIOB->ODR|= (1<<15);
tmp=((g_stc_mt6701_info.rx_data_buf[0]<<8)+g_stc_mt6701_info.rx_data_buf[1])>> 2;
g_stc_mt6701_info.dif_val= (g_stc_mt6701_info.u16_val + 0x3fff - g_stc_mt6701_info.u16_old_val) %0x3fff;
g_stc_mt6701_info.u16_old_val= g_stc_mt6701_info.u16_val;
g_stc_mt6701_info.sta= e_mt6701_idle;
}
测试结果数据不更新。
检查代码基本没有啥问题,单步调试
进入dma中断
回调
Spi的dma中断相应
Spi结束判定
异常
原因查找结果是找不到spi结束标志,上示波器测试时钟信号发现时钟信号并未在传输结束后结束,即时钟信号脉冲数超过17个,查找stm32手册找到原因在主模式且仅接收模式下spi未关闭前clk信号始终存在,但是此时已经完成了数据的接收,解决的方法有很多种,本文使用修改库的方法解决,修改文件如下
文件stm32f4xx_hal_spi.c,修改
修改前
static HAL_StatusTypeDefSPI_EndRxTransaction(SPI_HandleTypeDef *hspi, uint32_t Timeout, uint32_t Tickstart)
{
if((hspi->Init.Mode == SPI_MODE_MASTER) && ((hspi->Init.Direction== SPI_DIRECTION_1LINE)
|| (hspi->Init.Direction == SPI_DIRECTION_2LINES_RXONLY)))
{
/* Disable SPI peripheral */
__HAL_SPI_DISABLE(hspi);
}
/*Erratasheet: BSY bit may stay high at the end of a data transfer in Slave mode*/
if(hspi->Init.Mode == SPI_MODE_MASTER)
{
if (hspi->Init.Direction != SPI_DIRECTION_2LINES_RXONLY)
{
/* Control the BSY flag */
if (SPI_WaitFlagStateUntilTimeout(hspi, SPI_FLAG_BSY, RESET, Timeout,Tickstart) != HAL_OK)
{
SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_FLAG);
return HAL_TIMEOUT;
}
}
else
{
/* Wait the RXNE reset */
if (SPI_WaitFlagStateUntilTimeout(hspi, SPI_FLAG_RXNE, RESET, Timeout,Tickstart) != HAL_OK)
{
SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_FLAG);
return HAL_TIMEOUT;
}
}
}
else
{
/* Wait the RXNE reset */
if (SPI_WaitFlagStateUntilTimeout(hspi, SPI_FLAG_RXNE, RESET, Timeout,Tickstart) != HAL_OK)
{
SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_FLAG);
return HAL_TIMEOUT;
}
}
return HAL_OK;
}
//修改后
static HAL_StatusTypeDefSPI_EndRxTransaction(SPI_HandleTypeDef *hspi, uint32_t Timeout, uint32_t Tickstart)
{
if((hspi->Init.Mode == SPI_MODE_MASTER) && ((hspi->Init.Direction== SPI_DIRECTION_1LINE)
|| (hspi->Init.Direction == SPI_DIRECTION_2LINES_RXONLY)))
{
/* Disable SPI peripheral */
__HAL_SPI_DISABLE(hspi);
//修改的这里
/* USER CODE BEGIN Init */
if(hspi->Init.Direction== SPI_DIRECTION_2LINES_RXONLY)
{
returnHAL_OK;
}
/* USER CODE END Init */
//修改结束
}
/*Erratasheet: BSY bit may stay high at the end of a data transfer in Slave mode*/
if(hspi->Init.Mode == SPI_MODE_MASTER)
{
if (hspi->Init.Direction != SPI_DIRECTION_2LINES_RXONLY)
{
/* Control the BSY flag */
if (SPI_WaitFlagStateUntilTimeout(hspi,SPI_FLAG_BSY, RESET, Timeout, Tickstart) != HAL_OK)
{
SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_FLAG);
return HAL_TIMEOUT;
}
}
else
{
/* Wait the RXNE reset */
if (SPI_WaitFlagStateUntilTimeout(hspi, SPI_FLAG_RXNE, RESET, Timeout,Tickstart) != HAL_OK)
{
SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_FLAG);
return HAL_TIMEOUT;
}
}
}
else
{
/* Wait the RXNE reset */
if (SPI_WaitFlagStateUntilTimeout(hspi, SPI_FLAG_RXNE, RESET, Timeout,Tickstart) != HAL_OK)
{
SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_FLAG);
return HAL_TIMEOUT;
}
}
return HAL_OK;
}
再次测试:
遗留的问题:这种方式破坏了库的一致性,且使用cubemx重新编码后会出现覆盖问题,不是妥善的解决方式。
想了一种方法通过测试也能达到同样的效果且不修改库函数
void DMA1_Stream3_IRQHandler(void)
{
/*USER CODE BEGIN DMA1_Stream3_IRQn 0 */
SPI_HandleTypeDef*hspi = (SPI_HandleTypeDef *)(((DMA_HandleTypeDef*)&hdma_spi2_rx)->Parent);
__HAL_SPI_DISABLE(hspi);
if(SPI2== hspi->Instance)
{
fun_mt6701_dma_cb();
}
/*USER CODE END DMA1_Stream3_IRQn 0 */
HAL_DMA_IRQHandler(&hdma_spi2_rx);
/*USER CODE BEGIN DMA1_Stream3_IRQn 1 */
/*USER CODE END DMA1_Stream3_IRQn 1 */
}
这种方式需要将如下部分注释,原因是在中断入口处已经关闭spi有可能会正常进入中断但是不能保证每次均进入,故将fun_mt6701_dma_cb();放在DMA1_Stream3_IRQHandler函数中。
voidHAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)
{
if(SPI2== hspi->Instance)
{
//fun_mt6701_dma_cb();
}
}
测试结果如下
仅在DMA1_Stream3_IRQHandler函数关闭spi,HAL_SPI_RxCpltCallback保持中断回调:
void DMA1_Stream3_IRQHandler(void)
{
/*USER CODE BEGIN DMA1_Stream3_IRQn 0 */
SPI_HandleTypeDef*hspi = (SPI_HandleTypeDef *)(((DMA_HandleTypeDef*)&hdma_spi2_rx)->Parent);
__HAL_SPI_DISABLE(hspi);
// if(SPI2== hspi->Instance)
// {
// fun_mt6701_dma_cb();
// }
/*USER CODE END DMA1_Stream3_IRQn 0 */
HAL_DMA_IRQHandler(&hdma_spi2_rx);
/*USER CODE BEGIN DMA1_Stream3_IRQn 1 */
/*USER CODE END DMA1_Stream3_IRQn 1 */
}
void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef*hspi)
{
if(SPI2== hspi->Instance)
{
fun_mt6701_dma_cb();
}
}
测试结果,使能次数远大于中断回调次数,结果如下:
仅在DMA1_Stream3_IRQHandler函数关闭spi,且使用中断回调函数,HAL_SPI_RxCpltCallback保持注释中断回调:
void DMA1_Stream3_IRQHandler(void)
{
/*USER CODE BEGIN DMA1_Stream3_IRQn 0 */
SPI_HandleTypeDef*hspi = (SPI_HandleTypeDef *)(((DMA_HandleTypeDef*)&hdma_spi2_rx)->Parent);
__HAL_SPI_DISABLE(hspi);
if(SPI2== hspi->Instance)
{
fun_mt6701_dma_cb();
}
/*USER CODE END DMA1_Stream3_IRQn 0 */
HAL_DMA_IRQHandler(&hdma_spi2_rx);
/*USER CODE BEGIN DMA1_Stream3_IRQn 1 */
/*USER CODE END DMA1_Stream3_IRQn 1 */
}
voidHAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)
{
if(SPI2== hspi->Instance)
{
//fun_mt6701_dma_cb();
}
}
使能次数和中断回调次数相同,测试结果如下:
也可以试试ll库的方式解决,有兴趣的可以试试。
在此解释一下为什么一定要使用中断加dma的方式来读取mt6701的数据,我所使用的foc的框架是tim触发adc在adc中断中处理foc坐标变换,svpwm等,如果在中断中直接读取编码器的值会造成中断服务函数耗时较长,使用dma中断方式读取编码器数据,使foc具有更好的中断响应,提高转速上限。
电机驱动svpwm波形空载如下图所示,非常nice,这个传感器很优秀
附件是ioc文件和mt6701的手册。