STM32学习笔记(串口+DMA)
2020.4.20
-
大端模式(big endian):低地址存放高有效字节
-
小端模式(little endian):低字节存放低有效字节
设一个
int
形变量0x12345678 两个数就是一字节 高有效字节——>低有效字节: 12 34 56 78
低地址位 高低址位
大端: 12 34 56 78
小端: 78 56 34 12
STM32单片机的存储方式为小端模式。
int main(void ){
unsigned int x =0x12345678;
unsigned char *p = (unsigned char *)&x; //取首地址后转换为字符数组,各地址上存储的数据
printf("%0x %0x %0x %0x",p[0],p[1],p[2],p[3]); //且char为8bit 故两位两位区分
return 0;
}
//依次输出78 56 34 12,可以看出电脑存储数据也是小端排序
- 在C程序中常见
uint8_t
(实为unsigned char
,与char大小同为一个字节,仅多一个符号),uint32_t
(实为unsigned int
),uint16_t
(实为unsigned short int
)
typedef unsigned char uint8_t;
typedef unsigned short int uint16_t;
typedef unsigned int uint32_t;
typedef unsigned __INT64 uint64_t;
其中,unsigned char
无符号位,可以表示0~ 255。char
能表示的数据范围是-128~127。
实际应用:
衍生概念:
MSB: Most Significant Bit ——- 最高有效位
LSB: Least Significant Bit ——- 最低有效位
在SPI及其他通信协议中,该概念有具体使用。
-
assert_param();
函数的理解:#ifdef USE_FULL_ASSERT #define assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t*)__FILE__, __LINE__)) //双目运算符判断expr的值的真假 (void)0什么都不执行 void assert_failed(uint8_t* file, uint32_t line); #else #define assert_param(expr) ((void)0) #endif
真值判断函数: 作为一个函数运行过程中,对形参的有效性进行检查。
assert_param(IS_DMA_ALL_PERIPH(DMAy_Channelx));
中IS_DMA_ALL_PERIPH(DMAy_Channelx)
为对DMAy_Channelx
的认证过程,若有效则返回空函数。常见于函数体中,看了感觉没啥用。
-
串口配合DMA的使用方法回顾:
实现平台说明:
本实验使用STM32F103c8t6配合CP2102串口电平转换模块进行。
基于标准库
- DMA初始化时应预留的参数接口 , 在函数外部能修改通道、源地址、目标地址和传输数据量等几个参数,必要时做出声明。
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;
//使用串口接收数据的寄存器的地址((uint32_t)&取地址并强制类型转换)作为DMA传输的外设基地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)rx_buf;
//提前定义数组
rx_buf[RX_BUFF_SIZE]
作为DMA的数据存储地址(存储器)//通常习惯
uint8_t rx_buf[2048];
然后强制类型转换为uint32_t
,不懂。
在串口中断中操作DMA时,一般设置串口中断为空闲中断
DMA的接收中断设置为传输成功
USART_ITConfig( USARTx, USART_IT_IDLE , ENABLE );
DMA_ITConfig(DMA1_Stream6,DMA_IT_TC, ENABLE);
- DMA相关的功能函数
void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber)
//设置DMA通道中传输数据的数量
//在开启DMA时,应先关闭DMA,再设置传输数据缓存的大小,再开启DMA
uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx)
//得到DMA通道中剩余数据单元的数量
FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG)
//获取DMA的通道状态
在串口接收中操作DMA时,一般设置串口中断为空闲中断,串口数据流停止后总线空闲产生空闲中断。(在伴随RXNE位被置高后,IDLE位才会被置起,相应产生中断)
USART_ITConfig( USARTx, USART_IT_IDLE , ENABLE );
在串口中断中需要进行以下几件事
(应先判断中断)
> if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)
> {
> clear = USART1->SR; //或者使用USART_ClearITPendingBit();
> clear = USART1->DR;
> }
-
关闭串口接收DMA通道,防止数据干扰,便于重新配置DMA.
-
置位接收完成标志位。
-
处理接收buffer里的数据。
-
重新设置DMA接收字节数。(大于等于可接收的最大字节数)
-
开启DMA,等待下次接收数据。
DMA的接收中断设置为传输错误中断
DMA_ITConfig(DMA1_Channel5, DMA_IT_TE, ENABLE);
- 在串口发送中操作DMA时,对串口的操作被最大限度保留,只需要设置DMA为传输成功中断。
DMA_ITConfig(DMA1_Channel5, DMA_IT_TE, ENABLE);
在中断中的操作较为简单,只需要清除标志位,关闭DMA即可。
难点为调用数据发送函数的编写,需要调用DMA_Cmd()
函数开始传输。且需要对数据类型进行一定的处理。
-
在开启DMA时,应先关闭DMA,再设置传输数据缓存的大小,再开启DMA。 且串口接收中,DMA保持开启。
-
在串口发送时,DMA仅在数据传输时(发送函数中)打开,传输成功后关闭DMA。
//开启一次DMA传输
void MYDMA_Enable(DMA_Channel_TypeDef*DMA_CHx)
{
DMA_Cmd(DMA_CHx, DISABLE ); //关闭该通道
DMA_SetCurrDataCounter(DMA_CHx,DMA1_MEM_LEN);//设置缓存大小
DMA_Cmd(DMA_CHx, ENABLE); //开启该通道DMA
}
{
DMA_Cmd(DMA_CHx, DISABLE ); //关闭该通道
DMA_SetCurrDataCounter(DMA_CHx,DMA1_MEM_LEN);//设置缓存大小
DMA_Cmd(DMA_CHx, ENABLE); //开启该通道DMA
}
-
串口DMA总结:
接收:能在不打扰主程序的情况下,借道DMA将应该从串口传输的数据存入
rxbuffer
;侧面缓解了主机的运行压力,在进入IDLE
总线空闲之前不需要对数据进行任何处理,同时计算数据位数,实现了不定长数据接收。直接用串口接收数据时,在RXNE
位被置高后就需要接收数据
2020.4.21
串口接收的代码实现:
-
今日配置了一套串口接收,实现了接收上位机发码的基本功能,可以在缓存区收到正确的数据。
附上乱套的代码, 供我以后参考使用。
static void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel = DEBUG_USART_IRQ;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
uint8_t rx_buf[20];
uint8_t rx_buffer_test[20];
static void USARTx_DMA_Rx_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)rx_buf;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//DMA_DIR_PeripheralToMemory
DMA_InitStructure.DMA_BufferSize = USART_RX_BUFF_SIZE;
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_Circular;
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel5, &DMA_InitStructure);
DMA_ClearFlag(DMA1_FLAG_TC5);
DMA_ITConfig(DMA1_Channel5, DMA_IT_TE, ENABLE);
DMA_Cmd (DMA1_Channel5,ENABLE);
}
void USART_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);
DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
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(DEBUG_USARTx, &USART_InitStructure);
NVIC_Configuration();
USART_ITConfig(DEBUG_USARTx, USART_IT_IDLE, ENABLE);
USART_DMACmd(DEBUG_USARTx, USART_DMAReq_Rx, ENABLE);
USARTx_DMA_Rx_Config();
USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE);
USART_Cmd(DEBUG_USARTx, ENABLE);
}
自己写的注释拷过来乱码了。
//中断部分
void USART1_IRQHandler(void)
{
uint8_t clear = clear;
b++;
if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)
{
clear = USART1->SR;
clear = USART1->DR;
RxCounter = USART_RX_BUFF_SIZE - DMA_GetCurrDataCounter(DMA1_Channel5);
RxStatus = 1; //自行设置了接收完成标志位,在主函数中检测终端的发生
USART_ClearITPendingBit(USART1, USART_IT_IDLE);
DMA_Cmd(DMA1_Channel5, DISABLE);
}
}
//主函数中循环部分
while(1)
{
if(RxStatus == 1)
{
RxStatus = 0;
while(RxCounter--)//对缓存数组逐个操作
{
OLED_ShowString(0,48,rx_buf,16);
OLED_Refresh();
while(USART_GetFlagStatus(USART1, USART_FLAG_TC) != SET);
}
memset(rx_buf, 0, i); // 清空缓存区
RxCounter = 0; //置低
// DMA1_Channel5->CNDTR = BufferSize;
DMA_SetCurrDataCounter(DMA1_Channel5, USART_RX_BUFF_SIZE);//保持数据长度不变
DMA_Cmd(DMA1_Channel5, ENABLE);
}
}
我当时因为想把接收到的数据传给OLED屏幕来显示,函数端口要求导入uint8_t
变量(通常使用该变量储存字符型数据,详见2020.4.20
),便定义了uint8_t rx_buf[20]
作为DMA的缓存数组,且串口调试助手也使用了CHR发送格式。
最终能将字符正确显示到OLED屏幕端口。
时间过于晚,睡了睡了
2020.4.22
数据拆分宏定义:
我参考着匿名四轴上位机发码的例程,重新体会了一下数据传输过程中对不同类型数据的处理方法。
#define BYTE0(dwTemp) (*(char *)(&dwTemp))
#define BYTE1(dwTemp) (*((char *)(&dwTemp) + 1))
#define BYTE2(dwTemp) (*((char *)(&dwTemp) + 2))
#define BYTE3(dwTemp) (*((char *)(&dwTemp) + 3))
其中,默认的dwTemp
为float
类型(4Byte),可以拆分为四个指向char
的指针,并指出相应内容。
串口发送的函数实现:
我自己配置了串口发送的程序,预留DMA缓存区为uint8_t
的tx_buf[40]
。假设我有一组浮点型数组data[8]
需要由单片机发送至上位机,可以直接使用匿名四轴函数处理发送数据。
依次将每一个data
传入函数,函数将一个浮点型数据拆分重组为一个数据帧:包含四个帧头位、四个数据位和一个求和校验位。(个人理解,不规范命名),可以按要求读取单片机中的数据。
串口发送的代码实现 :
static void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel = DEBUG_USART_IRQ;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel4_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
uint8_t rx_buf[20];
uint8_t tx_buf[40];
static void USARTx_DMA_Tx_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
// RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
DMA_DeInit(DMA1_Channel4);
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&USART1->DR);
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)tx_buf;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitStructure.DMA_BufferSize = USART_TX_BUFF_SIZE;
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_VeryHigh;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel4, &DMA_InitStructure);
DMA_ClearFlag(DMA1_IT_GL4);
DMA_Cmd(DMA1_Channel4,DISABLE);
DMA_ITConfig(DMA1_Channel4,DMA_IT_TC, ENABLE);
}
static void USARTx_DMA_Rx_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)rx_buf;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC
DMA_InitStructure.DMA_BufferSize = USART_RX_BUFF_SIZE;
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_Mode = DMA_Mode_Circular;
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel5, &DMA_InitStructure);
DMA_ClearFlag(DMA1_FLAG_TC5);
DMA_ITConfig(DMA1_Channel5, DMA_IT_TE, ENABLE);
DMA_Cmd (DMA1_Channel5,DISABLE);
}
void USART_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);
DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
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(DEBUG_USARTx, &USART_InitStructure);
NVIC_Configuration();
USART_Cmd(DEBUG_USARTx, ENABLE);
USARTx_DMA_Rx_Config();
USARTx_DMA_Tx_Config();
USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE);
USART_ITConfig(DEBUG_USARTx, USART_IT_IDLE, ENABLE);
USART_DMACmd(DEBUG_USARTx, USART_DMAReq_Rx, ENABLE);
USART_DMACmd(DEBUG_USARTx, USART_DMAReq_Tx, ENABLE);
}
//**********************************************//
int Flag_Uart_Busy;
int check_flag=0;
uint8_t uart1_send_buff[60];
void New_Send_Data(uint8_t *data,uint16_t size)
{
extern int f;f++;
Flag_Uart_Busy = 1;
memcpy(tx_buf,data,size);
DMA1_Channel4->CNDTR=(uint16_t)size;
DMA_Cmd(DMA1_Channel4,ENABLE);
}
void send_odm_msg(float * data)
{
int i=0;
uint8_t sum=0;
uart1_send_buff[0] = 0xAA;
uart1_send_buff[1] = 0xAA;
uart1_send_buff[2] = 0xF1;
uart1_send_buff[3] = 4;
for(i=0;i<1;i++)
{
uart1_send_buff[i*4+0+4] = BYTE3(*(data+i));
uart1_send_buff[i*4+1+4] = BYTE2(*(data+i));
uart1_send_buff[i*4+2+4] = BYTE1(*(data+i));
uart1_send_buff[i*4+3+4] = BYTE0(*(data+i));
}
for(i=0; i<8; i++)
{
sum+=uart1_send_buff[i];
}
uart1_send_buff[8] = sum;
New_Send_Data(uart1_send_buff,9);
}
void send_check(uint16_t data)
{
uint8_t uart6_send_buff[8];
uint16_t sum=0,i;
uart6_send_buff[0] = 0xAA;
uart6_send_buff[1] = 0xAA;
uart6_send_buff[2] = 0xEF;
uart6_send_buff[3] = 2;
uart6_send_buff[4] = 0x10;
uart6_send_buff[5] = data;
for(i=0;i<6;i++)
{
sum+=uart6_send_buff[i];
}
uart6_send_buff[6] = sum;
New_Send_Data(uart6_send_buff,7);
}
void Send_CheckData(void)
{
if(check_flag)
{
send_check(check_flag);
check_flag=0;
}
}
//DMA传输完成中断,在这里清除标志位,关闭DMA。
void DMA1_Channel4_IRQHandler(void)
{
e++;
if(DMA_GetITStatus(DMA1_FLAG_TC4))
{
DMA_ClearFlag(DMA1_FLAG_GL4);//DMA1_FLAG_GL4应该包含TC位
DMA_Cmd(DMA1_Channel4,DISABLE);
TxStatus=1; //制造传输完成标识,给前台程序提示串口发送完成。
}
}
串口DMA的后记 ,总结:
- 配置过程中还犯了个低级错误,忘记给DMA中断分配中断优先级,导致进不去传输成功中断,无法正常工作。以后一定留心,有中断就有中断优先级分配表,减少智障几率。
- 对串口的回顾到此结束,串口是我接触STM32后使用的第一个通信协议,对比SPI和IIC不设时钟线,也不像CAN一样使用差分信号,但串口可与USB接口通过电平转换相适配,应用广泛;在设备调试过程中可以使用串口与上位机进行通讯,传递相应的数据,十分有用。个人见解,仅供参考,若有错误,请多包含。
今天的文章STM32学习笔记(串口+DMA)分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/68170.html