物理层:规定通讯系统中具有机械、电子功能部分的特性,确保原始数据在物理媒体的传输。其实就是硬件部分。
协议层:协议层主要规定通讯逻辑,统一收发双方的数据打包、解包标准。其实就是软件部分。
简单来说物理层规定我们用嘴巴还是用肢体来交流,协议层则规定我们用中文还是英文来交流。
RS232结构图
1、RS232 标准串口主要用于工业设备直接通信
2、电平转换芯片一般有 MAX3232,SP3232
USB转串口结构图
1、USB 转串口主要用于设备跟电脑通信
2、电平转换芯片一般有 CH340、PL2303、CP2102、FT232
3、使用的时候电脑端需要安装电平转换芯片的驱动
串口到串口
1、原生的串口通信主要是控制器跟串口的设备或者传感器通信,不需要经过电平转换芯片来转换电平,直接就用 TTL 电平通信
2、GPS 模块、GSM 模块、串口转 WIFI 模块、HC05 蓝牙模块
波特率是每秒传输多少个bit
串口功能框图
发送
USART_CR1:UE (USART 使能)、TE (发送使能)、RE (接收使能)
USART_SR:TXE, Transmit data register empty
USARTCR1:TXEIE//发送数据标志位
US ART_SR:TC, Transmission complete
USARTCR1:TCIE/发送完成标志位
波特率—每秒钟要发送多少数据bit/s
USART_BRR :波特率寄存器,分整数和小数部分 USART_CR1 : OVER8
USARTDIV:无符号的定点数
FPCLK:串口的时钟,注区分 APB2 和 APB1 两条总线
OVER8:过采样模式
串口时钟初始化结构体
typedef struct
{
uint16_t USART_Clock; /*!< Specifies whether the USART clock is enabled or disabled.
This parameter can be a value of @ref USART_Clock 同步时钟CR2_CLKEN*/
uint16_t USART_CPOL; /*!< Specifies the steady state of the serial clock.
This parameter can be a value of @ref USART_Clock_Polarity 极性 CR2_CPOL*/
uint16_t USART_CPHA; /*!< Specifies the clock transition on which the bit capture is made.
This parameter can be a value of @ref USART_Clock_Phase 相位 CR2_CPHA*/
uint16_t USART_LastBit; /*!< Specifies whether the clock pulse corresponding to the last transmitted
data bit (MSB) has to be output on the SCLK pin in synchronous mode.
This parameter can be a value of @ref USART_Last_Bit 最后一位的时钟脉冲 CR2_LBC*/
} USART_ClockInitTypeDef;
//串口初始化结构体
typedef struct
{
uint32_t USART_BaudRate; /*!< This member configures the USART communication baud rate.波特率
The baud rate is computed using the following formula:
- IntegerDivider = ((PCLKx) / (8 * (OVR8+1) * (USART_InitStruct->USART_BaudRate)))
- FractionalDivider = ((IntegerDivider - ((u32) IntegerDivider)) * 8 * (OVR8+1)) + 0.5
Where OVR8 is the "oversampling by 8 mode" configuration bit in the CR1 register. 字长*/
uint16_t USART_WordLength; /*!< Specifies the number of data bits transmitted or received in a frame.
This parameter can be a value of @ref USART_Word_Length */
uint16_t USART_StopBits; /*!< Specifies the number of stop bits transmitted.
This parameter can be a value of @ref USART_Stop_Bits 停止位*/
uint16_t USART_Parity; /*!< Specifies the parity mode.
This parameter can be a value of @ref USART_Parity校验控制
@note When parity is enabled, the computed parity is inserted
at the MSB position of the transmitted data (9th bit when
the word length is set to 9 data bits; 8th bit when the
word length is set to 8 data bits). */
uint16_t USART_Mode; /*!< Specifies whether the Receive or Transmit mode is enabled or disabled.
This parameter can be a value of @ref USART_Mode 模式选择*/
uint16_t USART_HardwareFlowControl; /*!< Specifies wether the hardware flow control mode is enabled
or disabled.
This parameter can be a value of @ref USART_Hardware_Flow_Control 硬件流控制*/
} USART_InitTypeDef;
常用的库函数
1-配置 GPIO 为具体的第二功能
void GPIO_Pin AF Config
(GPIO_TypeDef*GPIO x, uint 16_t GPIO_Pin Source, uint 8_t GPIO_AF)
2-中断配置函数
void US ART_IT Config
(US ART_TypeDef*US ART x, uint 16_tUS ART_IT,Functional State New State)
3-串口使能函数
void US ART_Cmd (US ART_TypeDef*US ART x,Functional State New State)
4-数据发送函数
void USART_SendData
(USART_TypeDef* USARTx, uint16_t Data)
5-数据接收函数
uint16_t USART_ReceiveData (USART_TypeDef* USARTx)
6-中断状态位获取函数 ITStatus USART_GetITStatus
(USART_TypeDef* USARTx, uint16_t USART_IT)
1-初始化串口需要用到的 GPIO, GPIO_InitTypeDef, GPIO_PinAFConfig ();
2-初始化串口, USART_InitTypeDef
3-中断配置
4-使能串口
5-编写发送和接收函数
6-编写中断服务函数
/**
* @brief 配置嵌套向量中断控制器NVIC串口中断服务函数
* @param 无
* @retval 无
*/
static void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* 嵌套向量中断控制器组选择 */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
/* 配置UART为中断源 */
NVIC_InitStructure.NVIC_IRQChannel = DEBUG_UART_IRQ;
/* 抢断优先级为1 */
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
/* 子优先级为1 */
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
/* 使能中断 */
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
/* 初始化配置NVIC */
NVIC_Init(&NVIC_InitStructure);
}
/**
* @brief DEBUG_UART GPIO 配置,工作模式配置。115200 8-N-1 ,中断接收模式
* @param 无
* @retval 无
*/
void Debug_UART_Config(void)
{
//定义初始化结构体
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
//第一步 初始化GPIO
RCC_AHB1PeriphClockCmd(DEBUG_UART_RX_GPIO_CLK|DEBUG_UART_TX_GPIO_CLK,ENABLE);
/* GPIO初始化 */
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
/* 配置Tx引脚为复用功能 */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Pin = DEBUG_UART_TX_PIN ;
GPIO_Init(DEBUG_UART_TX_GPIO_PORT, &GPIO_InitStructure);
/* 配置Rx引脚为复用功能 */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Pin = DEBUG_UART_RX_PIN;
GPIO_Init(DEBUG_UART_RX_GPIO_PORT, &GPIO_InitStructure);
//GPIO具体复用成什么第二功能
/* 连接 PXx 到 USARTx_Tx*/
GPIO_PinAFConfig(DEBUG_UART_RX_GPIO_PORT,DEBUG_UART_RX_SOURCE,DEBUG_UART_RX_AF);
/* 连接 PXx 到 USARTx__Rx*/
GPIO_PinAFConfig(DEBUG_UART_TX_GPIO_PORT,DEBUG_UART_TX_SOURCE,DEBUG_UART_TX_AF);
//第二步 配置配置串口初始化结构体
/* 配置串DEBUG_UART 模式 */
/* 使能 USART 时钟 */
RCC_APB1PeriphClockCmd(DEBUG_UART_CLK, ENABLE);
/* 波特率设置:DEBUG_UART_BAUDRATE */
USART_InitStructure.USART_BaudRate = DEBUG_UART_BAUDRATE;
/* 字长(数据位+校验位):8 */
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
/* 停止位:1个停止位 */
USART_InitStructure.USART_StopBits = USART_StopBits_1;
/* 校验位选择:不使用校验 */
USART_InitStructure.USART_Parity = USART_Parity_No;
/* 硬件流控制:不使用硬件流 */
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
/* USART模式控制:同时使能接收和发送 */
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
/* 完成USART初始化配置 */
USART_Init(DEBUG_UART, &USART_InitStructure);
//第三步 配置串口的接收中断
/* 嵌套向量中断控制器NVIC配置 */
NVIC_Configuration();
/* 使能串口接收中断 */
USART_ITConfig(DEBUG_UART, USART_IT_RXNE, ENABLE);
/* 第四步 使能串口 */
USART_Cmd(DEBUG_UART, ENABLE);
}
//第五步 发送和接收函数
/***************** 发送一个字符 **********************/
void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch)
{
/* 发送一个字节数据到USART */
USART_SendData(pUSARTx,ch);
/* 等待发送数据寄存器为空 */
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}
/***************** 发送字符串 **********************/
void Usart_SendString( USART_TypeDef * pUSARTx, char *str)
{
unsigned int k=0;
do
{
Usart_SendByte( pUSARTx, *(str + k) );
k++;
} while(*(str + k)!='\0');
/* 等待发送完成 */
while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET)
{}
}
串口1 GPIO时钟在AHB1,串口时钟在APB2,AHB1时钟为168M,APB2时钟为84M。
DMA接收中断:
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //接收数据寄存器非空中断
USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);//空闲线路检测中断
串口1抢占优先级为10 子优先级为0.未使用DMA.
串口2GPIO时钟在AHB1,串口时钟在APB1,AHB1时钟为168M,APB1时钟为42M。
串口2抢占优先级为10 子优先级为2.串口2无DMA.
串口3GPIO时钟在AHB1,串口时钟在APB1,AHB1时钟为168M,APB1时钟为42M。
串口3抢占优先级为10 子优先级为0.未使用DMA.
串口6GPIO时钟在AHB1,串口时钟在APB1,AHB1时钟为168M,APB2时钟为84M。
串口6抢占优先级为10 子优先级为2.未使用DMA.
USART_GetFlagStatus(USART1, USART_FLAG_RXNE);
3)RXNE(读数据寄存器非空),当该位被置 1 的时候,就是提示已经有数据被接收到了,并且可以读出来了。这时候我们要做的就是尽快去读取 USART_DR,通过读 USART_DR 可以将该位清零,也可以向该位写 0,直接清除。
//判断接收数据寄存器非空中断标志是否为0,为1证明有数据接收了,可以读。
USART_GetFlagStatus(USART1, USART_FLAG_TC);
TC(发送完成),当该位被置位的时候,表示 USART_DR 内的数据已经被发送完成了。如
果设置了这个位的中断,则会产生中断。该位也有两种清零方式: 1)读 USART_SR,写
USART_DR。 2)直接向该位写 0。
USART_GetFlagStatus(USART1, USART_FLAG_IDLE)
IDLE闲线路检测,当该位置1的时候,证明该线路空闲,需要进行清零,读 USART_SR,读
USART_DR
//当接收数据寄存器为0,判断串口1是否为空闲线路,串口1空闲时
xSemaphoreGiveFromISR(BinarySemaphore_Uart1, &xHigherPriorityTaskWoken);//中断级信号量释放函数
USART1->SR & 0X40//串口1发送中,等SR寄存器第6位为0退出循环
uint16_t Uart_Crc_Make(uint16_t size, uint8_t *buff)
{
uint16_t car, i;
uint16_t crc[2];
uint16_t CrcCodeTmp;
crc[0] = 0xff;
crc[1] = 0xff;
for ( i = 0; i < size; i++ ) {
car = buff[i];
car ^= crc[0];
if(car>256) //发现这里有小概率会溢出,增加直接返回
return 0;
crc[0] = crc[1] ^ crc_tab.TABLE2[car];
crc[1] = crc_tab.TABLE1[car];
}
/*
CrcCodeTmp=crc[0];
crc[0]=crc[1];
crc[1]=CrcCodeTmp;
return (*(uint16_t*)(&crc[0]));
*/
CrcCodeTmp = (crc[0]<<8)+crc[1];
return CrcCodeTmp;
}
/***********************************************************/
uint16_t Uart_crc_check( uint16_t data_len, uint8_t *data )
{
uint16_t Crc_Code;
uint16_t Crc_LowCode, Crc_HighCode;
Crc_Code = Uart_Crc_Make( data_len, data); //对接收数据计算CRC校验码
Crc_HighCode = data[ data_len ];
Crc_LowCode = data[ data_len + 1 ];
if( Crc_Code == ( Crc_HighCode * 0x100 + Crc_LowCode ) ) //校验码正确
return 1;
else
return 0;
}
/***************************************
us延时函数
精确测量168m主频 time_us=1 延时1us
**************************************/
void Uart_Delay_nus(uint16_t time_us)
{
uint16_t k1,k2;
for(k1=time_us;k1>0;k1--)
{
for(k2=33;k2>0;k2--)
{
}
}
}
疑问:任务、信号量、中断是如何关联在一起?
在STM32f407中,任务、队列、信号量和中断都是多任务操作系统中重要的概念。
任务是指单独运行的程序,可以视为一个独立的线程。在STM32f407中,任务可以通过FreeRTOS操作系统创建和管理。每个任务都有自己的任务栈和任务控制块,可以通过FreeRTOS提供的API函数实现任务的创建、删除、切换等操作。
队列是一种数据结构,用于在不同任务之间传递数据。在STM32f407中,队列可以通过FreeRTOS操作系统创建和管理。数据可以通过队列的发送和接收操作在不同任务之间传递。
信号量是一种同步机制,用于在不同任务之间协调执行。在STM32f407中,信号量可以通过FreeRTOS操作系统创建和管理。可以使用信号量来控制任务的访问资源、保证任务的同步和互斥等。
中断是一种硬件机制,用于响应外部事件。在STM32f407中,中断可以通过NVIC(嵌套向量中断控制器)控制。可以使用中断来响应外部事件,将其转换为任务执行的机会。
在STM32f407中,任务、队列、信号量和中断是通过FreeRTOS操作系统紧密关联,并协同工作,实现复杂的多任务应用程序。任务间通过队列和信号量传递数据,中断可以响应外部事件并激活相应的任务执行。
中断如何响应外部事件并激活相应的任务执行?
在STM32f407中,中断可以响应外部事件并激活相应的任务执行,具体的实现过程如下:
-
为外部事件分配一个中断向量号,将其与中断处理函数关联。中断处理函数是一个特殊的函数,当相应的中断发生时,该函数将自动运行。
-
配置NVIC(嵌套向量中断控制器),使其能够响应相应的中断向量号。通过NVIC可以开启和关闭中断,优先级控制等。
-
当相应的外部事件发生时,中断向量号将被发送到NVIC,NVIC将检查中断优先级并根据优先级决定是否触发中断。
-
如果中断被触发,NVIC将跳转到相应的中断处理函数,并执行其中的代码。中断处理函数会立即保存中断现场,包括程序计数器、状态寄存器等,并执行设定的处理代码。
-
中断处理函数中的代码通常包括执行必要的操作并激活相关的任务。例如,当一个读取外部传感器的中断发生时,中断处理函数可以读取传感器数据并将其发送到任务队列中,以便其他任务能够使用数据进行后续处理。
-
如果存在多个任务同时等待相同的数据,则任务调度器将根据优先级切换到相应的任务,并使用队列接收操作获取数据进行处理。如果相应的数据不可用,则任务将被阻塞,直到数据可用。
通过这种方式,中断可以响应外部事件,并激活相关的任务进行相应的处理,从而实现了多任务应用程序的复杂功能。
中断处理函数如何将数据发送到任务队列中?
中断处理函数可以使用任务通知或者队列来将数据发送到任务队列中。
方式一:任务通知
任务通知是一种轻量级的同步机制,可以通过事件通知来通信。中断处理函数可以使用xTaskNotifyFromISR()函数向任务发送通知,并将数据作为通知的参数传递。任务可以使用xTaskNotifyWait()函数来等待通知,接收数据。
方式二:队列
队列是一种FIFO(先进先出)数据结构,可以将数据从一个任务传递到另一个任务。中断处理函数可以使用xQueueSendToBackFromISR()函数将数据发送到队列中,任务可以使用xQueueReceive()函数从队列中接收数据。需要注意的是,在中断处理函数中向队列发送数据时,必须使用xQueueSendToBackFromISR()函数,否则可能会造成数据的丢失或者死锁。
无论使用哪种方式,中断处理函数都必须使用FromISR后缀的函数来发送数据,并在中断服务例程中使用portYIELD_FROM_ISR()函数来手动切换任务。
RSM485ECHT
RSM485ECHT是一款RS485转换器,用于将RS485信号转换为TTL或CMOS信号。在使用RSM485ECHT时,需要根据具体的应用场景选择相应的连接方式。
如果需要将RS485信号转换为TTL或CMOS信号,可以将RSM485ECHT插入RS485总线上,接线时需要注意A、B两个端口的连接。
如果需要使用RSM485ECHT进行数据采集或通信,需要通过串口连接RSM485ECHT,并使用相应的驱动程序进行初始化,以确保正常的数据通信。
对于RSM485ECHT这种转换器设备,一般情况下不需要进行专门的初始化操作。在连接串口后,只需要正确设置串口的参数(比如波特率、数据位、校验位、停止位等),并按照协议要求对数据进行编码和解码即可实现数据的传输。
如果需要进行特殊设置(比如修改设备的地址、通信方式等),一般情况下需要通过相应的配置工具或指令进行设置。
SN74LVC4245A
SN74LVC4245A是一款双向总线转换器,可以实现TTL/CMOS信号和3.3V或5V的CMOS信号之间的双向转换。该芯片具有低功耗、高速、高抗干扰等优点,是电子产品设计中常用的芯片之一。
SN74LVC4245A操作简单,只需要将TTL/CMOS信号输入到DIR控制端,即可实现信号的双向转换。DIR为高电平时,从A端向B端传输数据;DIR为低电平时,从B端向A端传输数据。此外,SN74LVC4245A还有一个使能端(OE),使能端为低电平时,输出端口的输出为高阻态。
使用SN74LVC4245A需要注意以下几点:
-
输入信号必须与芯片供电电压匹配,VCC为3.3V或5V。
-
要注意输入信号的幅值,如果输入信号的幅值超过芯片的供电电压,可能会损坏芯片。
-
DIR控制端、使能端OE和输出端口均需要连接适当的上拉或下拉电阻,以确保信号的稳定性。
总之,SN74LVC4245A是一款简单易用、功能强大的总线转换器芯片,可以在各种数字电路应用中起到极好的作用。在使用时需要注意信号电平、幅值、控制端和输出端口的连接方式等因素。