我是靠谱客的博主 超帅过客,最近开发中收集的这篇文章主要介绍CubeMX STM32串口1DMA使用IDLE中断接收、串口2DMA接收DMX512信号(标准)DMX512协议CubeMX代码部分总结,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

CubeMX STM32串口1DMA使用IDLE中断接收、串口2DMA收发DMX512信号(标准)

  • DMX512协议
  • CubeMX
  • 代码部分
    • 串口1
    • 串口2
    • 外部中断
    • 定时器1
  • 总结

DMX512协议

这是我第一次写文章,请大家多多指教。
最近遇到了一些疑问,还请大家帮忙瞧瞧。

DMX(Digital MultipleX),意为多路数字传输,采用的是单向异步串行传输,是美国舞台灯光协会(usITT)于1990年发布的灯光控制器与灯具设备进行数据传输的工业标准,全称是USITTDMX512(1990)。
DMX512波特率:250Kbps,即每比特宽度是4 us。
数据帧格式:11位(1位开始位,8位数据,2位停止位)。
标准的DMX512信号包括一个MTBP位、一个Break位、一个MAB位、一个SC和512个数据帧。其中Break位不小于88us,MAB位不小于8us。时序图如下:
链接: link.
在这里插入图片描述

CubeMX

本实验采用的芯片是STM32F103C8T6。
下面是CubeMX的部分配置:
打开外部时钟,本实验HCLK为64MHZ。在这里插入图片描述
配置uart1,波特率为115200,打开中断,添加DMA。在这里插入图片描述
配置uart2,波特率为250000,打开中断,添加DMA,停止位2位。在这里插入图片描述
配置定时器1,64MHz/(63+1)/(3+1) = 250kHz,即4us,NVIC打开中断,这里我配置优先级为1。
在这里插入图片描述

配置完成后,生成Keil工程。

代码部分

串口1

在usart.c/h中,自行添加打开IDLE中断函数和DMA接收。

#define BUFFER_SIZE  100
uint8_t aRxBuffer[BUFFER_SIZE];
volatile uint8_t rx_len = 0,recv_end_flag = 0;
void MX_USART1_UART_Init(void)
{
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    Error_Handler();
  }
  __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);//使能idle中断
  HAL_UART_Receive_DMA(&huart1,aRxBuffer,BUFFER_SIZE);//打开DMA接收,数据存入rx_buffer数组中。
}

在stm32f1xx_it.c中编写串口1的接收中断函数,这里引用了:
链接: link.

void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
uint8_t temp;
if(USART1 == huart1.Instance)
{
if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE) != RESET)
  {
   recv_end_flag = 1;  // 接受完成标志位置1 
   __HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除标志位
   HAL_UART_DMAStop(&huart1);
   temp  =  __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);// 获取DMA中未传输的数据个数
   rx_len =  BUFFER_SIZE - temp; //总计数减去未传输的数据个数,得到已经接收的数据个数
   HAL_UART_Receive_DMA(&huart1,aRxBuffer,BUFFER_SIZE);//重新打开DMA接收
  }
}
HAL_UART_IRQHandler(&huart1);
}

然后直接在main.c大循环里面加入

if(recv_end_flag == 1)
  {
   DMA_USART1_Send(aRxBuffer,rx_len);
   recv_end_flag = 0;
   rx_len = 0;
  }

串口2

在usart.c/h中,串口2初始化和发送DMX信号的编写,DMX的break和MAB信号在定时中断中产生。DMX信号发送需要对TX引脚进行普通输出模式和复用输出模式的配置。

#define DMXBUFFER_SIZE  512
uint8_t DMXrxbuffer[DMXBUFFER_SIZE];
volatile uint8_t DMXrecv_start_flag = 0,DMXrecv_end_flag = 0;
void MX_USART2_UART_Init(void)
{
  huart2.Instance = USART2;
  huart2.Init.BaudRate = 250000;
  huart2.Init.WordLength = UART_WORDLENGTH_8B;
  huart2.Init.StopBits = UART_STOPBITS_2;
  huart2.Init.Parity = UART_PARITY_NONE;
  huart2.Init.Mode = UART_MODE_TX_RX;
  huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart2.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart2) != HAL_OK)
  {
    Error_Handler();
  }
  __HAL_UART_ENABLE_IT(&huart2, UART_IT_TC);//使能发送完成中断
}

void TX_gpioConfig(uint8_t tx_select)//普通输出模式和复用输出模式
{
  GPIO_InitTypeDef GPIO_InitStruct = {0}; 
 /* GPIO Ports Clock Enable */
// __HAL_RCC_GPIOA_CLK_ENABLE();
 /*Configure GPIO pin : PA2 */
  GPIO_InitStruct.Pin = GPIO_PIN_2;
  if(tx_select == 1) {GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;}
  if(tx_select == 2) {GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;}
 
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;  
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}

void DMX512_Send(uint8_t *buf,uint16_t len)//DMX信号发送
{
  if(DMXsend_start_flag == 0)
 {
  DMXsend_start_flag = 1;
  HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_RESET); //拉低tx引脚,改为普通输出模式
  TX_gpioConfig(1); //普通输出模式
  HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_SET); //使能485发送
  dmx_mode = 2;
  tim1_count = 0;
  HAL_TIM_Base_Start_IT(&htim1);//打开定时器
  delay_us(100);// 延时us,等待计数结束
 }
   if(DMX_break == 1)
 {
  HAL_UART_Transmit_DMA(&huart2,buf,len);
  if(DMXsend_end_flag == 1)
  {
   DMX_break = 0;
   DMXsend_end_flag = 0;
   DMXsend_start_flag = 0;
   dmx_mode = 1;
   HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET); //发送完成,使能485接收
  }
 }
}

在stm32f1xx_it.c中,有多处中断,包括串口1、串口2、定时器1、外部中断。

void USART2_IRQHandler(void)//DMX信号接收
{
 /* USER CODE BEGIN USART2_IRQn 0 */
 static uint8_t clean_idle;
 if(USART2 == huart2.Instance)
 {
   DMXsend_end_flag = 1;// 接受完成标志位置1 
  if(__HAL_UART_GET_FLAG(&huart2,UART_FLAG_ORE) != RESET)
  {
   clean_idle = USART2->SR;
   clean_idle = USART2->DR;
   __HAL_UART_CLEAR_OREFLAG(&huart2);
  }
 }
  HAL_UART_IRQHandler(&huart2);
}

在主函数的大循环中,把接收到的数据通过uart1发送。

外部中断

这里接了PA8引脚到串口2的RX引脚上,用来捕捉break和MAB信号。

void EXTI9_5_IRQHandler(void)
{
 if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_8) != RESET)
 {
  __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_8);
  if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_8)==1)//上升沿
  { 
   if(tim1_count2>23)//检测到break
   {
    break_notic = 1;
    HAL_UART_DMAStop(&huart2);
    if(rece_notic == 1) {DMXrecv_end_flag = 1;} 
   }
   if(break_notic == 2)
   {
    break_notic = 0;
    HAL_UART_Receive_DMA(&huart2,DMXrxbuffer,DMXBUFFER_SIZE);//打开DMA接收
    HAL_TIM_Base_Stop_IT(&htim1);//关闭定时器
   }
   tim1_count2 = 0;
  }
  else if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_8)==0)//下降沿
  {
   
   if(break_notic == 1&&tim1_count2<=4)//检测到MAB
   {
    break_notic = 2;
    rece_notic = 1;
   }
   tim1_count2= 0;
   HAL_TIM_Base_Start_IT(&htim1);//打开定时器
  }
 }
 HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_8);
}

定时器1

定时器1主要用做处理DMX信号接收和发送的时间基准,接收时,dmx_mode为1;发送时为2。

void TIM1_UP_IRQHandler(void)
{
  if(TIM1 == htim1.Instance)
 {
   switch(dmx_mode)
  {
   case 1:
   {
    tim1_count2++;
    if(tim1_count2>60) HAL_TIM_Base_Stop_IT(&htim1);//关闭定时器
   }
   break;
   case 2:
   {
    if(tim1_count <= 20)//break信号>88us
    {   
     HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_RESET); 
    }
    else if(tim1_count == 21||tim1_count ==22)//MAB信号>8us
    {
     HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_SET);
    }
    else if(tim1_count >= 23&&tim1_count <=30)//模拟SC信号,此处应该还有更好办法处理SC信号
    {
     HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_RESET); 
    }
    else if(tim1_count >= 31&&tim1_count <=33)//tx引脚转换为复用模式
    {
     TX_gpioConfig(2);
    }
    else
    {
     DMX_break = 1;
     tim1_count = 0;
     HAL_TIM_Base_Stop_IT(&htim1);//关闭定时器
    }
    tim1_count++;
   }
   break;
   default: break;
  }
 }
 HAL_TIM_IRQHandler(&htim1);
}

总结

附上几张测试结果:
这是DMX信号发送的时序图:在这里插入图片描述
以下是数据接收结果。
单片机与单片机直之间通信问题不大,主要问题是接收DMX192控制台或者ART-NET的DMX信号。本文用外部中断+定时器基准处理break和MAB信号,使得程序适用性更强。在这里插入图片描述
最后总结:
___本文内容主要尝试采用STM32单片机实现标准型的DMX512信号的发送和接收,具体思路总结一下:
发送信号:开启定时器1,4us进入一次中断服务函数;配置USART2的TX引脚;开始88us配置为普通输出模式,输出为低;接下来8us为普通输出模式,输出为高;之后配置为复用输出模式,再开启DMA发送数据;发送过程不需要延时:44us*512=22528us(不占用CPU),只需要判断是否进入发送完成中断。
接收信号:STM32单片机打开外部中断(模式为上升沿或者下降沿触发),当检测为下降沿时,打开定时器计数,检测到上升沿时,查看计数值,若计算出大于88us,则为break信号;继续捕捉下降沿,即为MAB信号,随后就可以打开DMA接收。当下一个break信号来临时,也就是说已经接收好第一个DMX信号,同时也是下一个信号的开始。
实现过程中遇到很多坑,如有错误,请指出。

最后

以上就是超帅过客为你收集整理的CubeMX STM32串口1DMA使用IDLE中断接收、串口2DMA接收DMX512信号(标准)DMX512协议CubeMX代码部分总结的全部内容,希望文章能够帮你解决CubeMX STM32串口1DMA使用IDLE中断接收、串口2DMA接收DMX512信号(标准)DMX512协议CubeMX代码部分总结所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(69)

评论列表共有 0 条评论

立即
投稿
返回
顶部