类似于【广播 对讲 电话】
不是有两根线就是全双工,而是输入和输出都有对应的数据线。
区分同步/异步通信的根本:判断是否有时钟信号(时钟线)。如果有时钟线则是同步通信,如果没有时钟线则是异步通信。
实际上就是按位来对数据进行发送和接收。
MAX323:将RS232电平---》TTL/CMOS电平
USB/串口转换电路CH340C:USB电平---》TTL/CMOS电平
注意:异步通信协议中是没有使用到时钟线(SCLK)
USART虽然是同步和异步都可以进行使用,但是我们大多数情况下使用的都是异步通信。
数据要先放在DR寄存器中,然后再通过DR寄存器操作CPU
baud:波特率是用户自己定义的,然后求出USARTDIV,然后分为整数部分和小数部分,在存储到USART_BRR寄存器
因为小数部分和整数部分都要往左移动4位,所以将整个值*16
1)具体传输多少位取决于寄存器USART_CR1中的位12M【字长】
2)设置好控制和波特率寄存器后,往该寄存器写入数据即可发送,接收数据则读该寄存器
根据TC位(发送完成位)可以知道能否发数据,根据RXNE位(读数据寄存器非空)知道是否收到数据。
对USART进行初始化
USART的中断使能函数
这个函数是非阻塞式的,没有执行完也可以出来
没有开启中断的UASRT
这个函数是阻塞式的,没有执行完不可以出来
相关的设置参数和开启usart的中断
1)使能USART1和对应的IO时钟
2)初始化IO
3)使能USART1中断,设置NVIC优先级
中断回调服务函数
2)HAL_UART_iRQHandler()会清除中断
串口接收数据的回调函数
注意点:我们的串口对应的应该是RX--》TX,TX--》RX
我们要先查看原理图中是否已经帮我们接反了,如果没有则需要我们自己手动接反。
我们使用的STM32F103C8xx直接接入USB口即可
由上面分析可知,我们使能PA9和PA10即可
MCU的CPU一个字节一个字节的将要发送的内容丢给串口模块,然后看着串口模块将这个字节发送出去,然后CPU再去拿下一个字节来丢给串口模块。直到本次要发送的所有字节全部发完,CPU才会去做其他事。
MCU的CPU向串口模块丢一个字节,然后串口模块慢慢发,CPU丢完这个字节后会跳出去做其他事情,等串口模块发完这个字节后会生成一个中断,中断会通知CPU过来继续丢下一个字节。
- HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
- {
- uint16_t *tmp;
- uint32_t tickstart = 0U;
-
- /* Check that a Tx process is not already ongoing */
- //查看状态
- if (huart->gState == HAL_UART_STATE_READY)
- {
- if ((pData == NULL) || (Size == 0U))
- {
- return HAL_ERROR;
- }
-
- /* Process Locked */
- //将串口模块锁住了,将变量的值设置为"HAL_LOCKED"
- __HAL_LOCK(huart);
-
- huart->ErrorCode = HAL_UART_ERROR_NONE;
- huart->gState = HAL_UART_STATE_BUSY_TX;
-
- /* Init tickstart for timeout managment */
- //获取当前的时候,用于判断是否超时
- tickstart = HAL_GetTick();
-
- huart->TxXferSize = Size;//表示要发送的个数
- huart->TxXferCount = Size;//还要发送的个数=总个数-已经发送
- while (huart->TxXferCount > 0U)
- {
- huart->TxXferCount--;
- if (huart->Init.WordLength == UART_WORDLENGTH_9B)
- {
- //等待标志发生,查看是否超时
- if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TXE, RESET, tickstart, Timeout) != HAL_OK)
- {
- return HAL_TIMEOUT;//超时
- }
- tmp = (uint16_t *) pData;
- huart->Instance->DR = (*tmp & (uint16_t)0x01FF);
- if (huart->Init.Parity == UART_PARITY_NONE)
- {
- pData += 2U;
- }
- else
- {
- pData += 1U;
- }
- }
- else
- {
- if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TXE, RESET, tickstart, Timeout) != HAL_OK)
- {
- return HAL_TIMEOUT;
- }
- //真正执行操作的代码
- huart->Instance->DR = (*pData++ & (uint8_t)0xFF);
- }
- }
- //查看发送是否已经完成【阻塞等待串口发完】
- if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TC, RESET, tickstart, Timeout) != HAL_OK)
- {
- return HAL_TIMEOUT;
- }
-
- /* At end of Tx process, restore huart->gState to Ready */
- huart->gState = HAL_UART_STATE_READY;
-
- /* Process Unlocked */
- //解锁
- __HAL_UNLOCK(huart);
-
- return HAL_OK;
- }
- else
- {
- return HAL_BUSY;
- }
- }
- HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
- {
- uint16_t *tmp;
- uint32_t tickstart = 0U;
-
- /* Check that a Rx process is not already ongoing */
- if (huart->RxState == HAL_UART_STATE_READY)
- {
- if ((pData == NULL) || (Size == 0U))
- {
- return HAL_ERROR;
- }
-
- /* Process Locked */
- __HAL_LOCK(huart);
-
- huart->ErrorCode = HAL_UART_ERROR_NONE;
- huart->RxState = HAL_UART_STATE_BUSY_RX;
-
- /* Init tickstart for timeout managment */
- tickstart = HAL_GetTick();
-
- huart->RxXferSize = Size;
- huart->RxXferCount = Size;
-
- /* Check the remain data to be received */
- while (huart->RxXferCount > 0U)
- {
- huart->RxXferCount--;
- if (huart->Init.WordLength == UART_WORDLENGTH_9B)
- {
- //检查UART_FLAG_RXNE是否为空,如果为非空,表示接收到数据
- if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_RXNE, RESET, tickstart, Timeout) != HAL_OK)
- {
- return HAL_TIMEOUT;
- }
- tmp = (uint16_t *) pData;
- if (huart->Init.Parity == UART_PARITY_NONE)
- {
- *tmp = (uint16_t)(huart->Instance->DR & (uint16_t)0x01FF);
- pData += 2U;
- }
- else
- {
- *tmp = (uint16_t)(huart->Instance->DR & (uint16_t)0x00FF);
- pData += 1U;
- }
-
- }
- else
- {
- if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_RXNE, RESET, tickstart, Timeout) != HAL_OK)
- {
- return HAL_TIMEOUT;
- }
- if (huart->Init.Parity == UART_PARITY_NONE)
- {
- *pData++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x00FF);
- }
- else
- {
- *pData++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x007F);
- }
-
- }
- }
-
- /* At end of Rx process, restore huart->RxState to Ready */
- huart->RxState = HAL_UART_STATE_READY;
-
- /* Process Unlocked */
- __HAL_UNLOCK(huart);
-
- return HAL_OK;
- }
如果我们设置的延时时间太长,则我们可能会在延时的时候错过一些字符的发送和接收。
- int main(void)
- {
- //定义一个要进行发送的数据
- uint8_t sbuf[8]="stm32";
- //定义一个要进行接收的数据
- uint8_t rbuf[20]="";
- HAL_Init();
- SystemClock_Config();
- MX_GPIO_Init();
- MX_USART1_UART_Init();
- while (1)
- {
- //第一个参数要传入的是地址
- //size:要发送的大小
- //0x0000ffff:超时时间
- // HAL_UART_Transmit(&huart1,sbuf,5,0x0000ffff);
- // HAL_Delay(1000);
-
- //阻塞式的接收:接收一个后马上接着下一个接收
- //此方法最好一次发送一个bit
- HAL_UART_Receive(&huart1,rbuf,1,0x0000ffff);
- //将接收到的数值发送出来
- HAL_UART_Transmit(&huart1,rbuf,1,0x0000ffff);
- //这个延时时间不能太久,要不然可能会在延时的时候接收了一个字符,从而错过这个
- //HAL_Delay(100);
- }
- }
阻塞式的发送实际用的很多,因为编程简单。缺陷是浪费高速CPU的部分性能,没有追求到串口发送和整个系统性能的最高。
这个函数没有超时时间,因为我们不用等。
注意点::要打开中断
- HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
- {
- /* Check that a Tx process is not already ongoing */
- if (huart->gState == HAL_UART_STATE_READY)
- {
- if ((pData == NULL) || (Size == 0U))
- {
- return HAL_ERROR;
- }
-
- /* Process Locked */
- __HAL_LOCK(huart);
-
- //因为我们使用中断方式,则需要在串口内部定义一个buf,使得buf指向data的地址
- huart->pTxBuffPtr = pData;
- huart->TxXferSize = Size;
- huart->TxXferCount = Size;
-
- huart->ErrorCode = HAL_UART_ERROR_NONE;
- huart->gState = HAL_UART_STATE_BUSY_TX;
-
- /* Process Unlocked */
- __HAL_UNLOCK(huart);
-
- /* Enable the UART Transmit data register empty Interrupt */
- //使能UART发送数据寄存器空中断
- __HAL_UART_ENABLE_IT(huart, UART_IT_TXE);
-
- return HAL_OK;
- }
- else
- {
- return HAL_BUSY;
- }
- }
我们使用这个中断式的应该打开Usart的中断
- int main(void)
- {
- //定义一个要进行发送的数据
- uint8_t sbuf[8]="stm32";
- //定义一个要进行接收的数据
- uint8_t rbuf[20]="";
- HAL_Init();
- SystemClock_Config();
- MX_GPIO_Init();
- MX_USART1_UART_Init();
- while (1)
- {
- /**
- 中断式发送和接收
- */
- HAL_UART_Transmit_IT(&huart1,sbuf,5);
- HAL_Delay(100);
- }
- }
printf的实现其实就是重载fputc函数
- #ifdef __GNUC__
- #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
- #else
- #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
- #endif
- /**
- * @brief Retargets the C library printf function to the USART.
- * @param None
- * @retval None
- */
- PUTCHAR_PROTOTYPE
- {
- /* Place your implementation of fputc here */
- /* e.g. write a character to the EVAL_COM1 and Loop until the end of transmission */
- HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
- return ch;
- }
一定一定要勾选User MicroLIB
我们使用到了fputc,其中FILE中是定义在
中的宏定义 定义在usart.h中
- int main(void)
- {
- /* USER CODE BEGIN 1 */
- //定义一个要进行发送的数据
- uint8_t sbuf[8]="stm32";
- //定义一个要进行接收的数据
- uint8_t rbuf[20]="";
- HAL_Init();
- SystemClock_Config();
- MX_GPIO_Init();
- MX_USART1_UART_Init();
- while (1)
- {
- float a=4.532;
- printf("a=%f\n",a);
- printf("test\r");
- }
- }
CPU等着过来。所以当CPU临时去处理其他事情时,可能会错过一些串口的输出。所以我们基本上不用。如果真的要使用,则要协调好延时和串口的接收和发送。
使用中断的方式向串口发送数据并且输出
在main函数中
- //rc用来暂存和处理串口接收到的字节内容的
- uint8_t receive_char;
-
- //这个就是HAL库对接的中断处理程序
- void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
- {
- //判断是否为usart1
- if(huart->Instance == USART1)
- {
- //这里就是真正的中断处理代码
- //我们这里的处理就是接收到一个字节后原封不动的发回去
- HAL_UART_Transmit(&huart1, &receive_char, 1,0xFF);//发送字符
- //等待本次接收完毕,同时开启下一次接收【更新receive_char】
- //HAL_UART_Receive_IT:开启中断处理流程
- while(HAL_UART_Receive_IT(&huart1, &receive_char, 1) != HAL_OK);
- }
-
- }
由上面分析可以知道,我们在进入中断处理函数中,是将接收到的字符发送出去,所以我们需要在外部先接收到字符【触发中断】,才可以发送。所以我们需要在其他模块初始化的位置先进入一次中断,接收串口发送的数据。
此代码:在我们不向串口发送字符时,每3s发送一次a,当我们向串口发送数据时,会马上在串口输出。
- int main(void)
- {
- HAL_Init();
- SystemClock_Config();
- MX_GPIO_Init();
- MX_USART1_UART_Init();
- //从此处进入中断处理程序
- HAL_UART_Receive_IT(&huart1, &receive_char, 1);
- while (1)
- {
- float a=4.532;
- printf("a=%f\n",a);
- HAL_Delay(3000);
- }
- }
-
- //这个就是HAL库对接的中断处理程序
- void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
- {
- //判断是否为usart1
- if(huart->Instance == USART1)
- {
- //这里就是真正的中断处理代码
- //我们这里的处理就是接收到一个字节后原封不动的发回去
- HAL_UART_Transmit(&huart1, &receive_char, 1,0xFF);//发送字符
- //等待本次接收完毕,同时开启下一次接收【更新receive_char】
- //HAL_UART_Receive_IT:开启中断处理流程
- while(HAL_UART_Receive_IT(&huart1, &receive_char, 1) != HAL_OK);
- }
-
- }
在初始化部分已经将中断连接起来,到时候产生中断则直接进入【HAL_UART_RxCpltCallback】这个函数
命令shell:发送一个命令返回一个回应
(1)指令集:指令1:add 指令2:sub
(2)指令结束符:';'【定义普通指令中不可能出现的】
(3)指令中遇到回车和空格、Tab等特殊字符怎么办
值存储再buf中的第几个字节
- /* USER CODE BEGIN 4 */
- //这个就是HAL库对接的中断处理程序
- void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
- {
- //判断是否为usart1
- if(huart->Instance == USART1)
- {
- //将receive_char的数值存放再buf中
- rev_buf[tindex]=receive_char;//暂存
- tindex++;//指向下一个
- while(HAL_UART_Receive_IT(&huart1, &receive_char, 1) != HAL_OK);
- }
-
- }
1)判断指令
2)buf和index要记得清空