概述
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代码部分总结所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复