1、USB硬件初始化
底层初始化使用的CUBEMX,库函数版本F4_V1.25.具体配置如下
2、USB函数封装
USB通信中真正的收发数据都是在USB中断中进行的,所以为了确认USB不会被其他中断打断,将USB的抢占优先级设定为0。
先说一下实现的大概思路吧。
只是发送(MCU -> PC)的话,用到了一个消息队列,一个线程。
1、创建一个消息队列tx_mq,一个发送线程tx_thread
2、用户想要发送消息的时候,将消息存入tx_mq中。存入的消息满足一定条件时将消息发送至tx_thread中进行发送
接受(PC->MCU)的话同理。先将接收到的消息存入接受的消息队列,然后转发到处理该消息的线程。
下面的就是用户将消息存入消息队列实现的代码。里面的信号量主要作用是 防止重入该函数。
#include "usbd_consolse.h"
#include "usbd_thread.h"
static uint8_t usbd_consolse_is_init = 0;
rt_sem_t usbd_con_bin_sem = RT_NULL;
msg_t message_to_print;
void usbd_console_Init()
{
if(usbd_consolse_is_init) return;
usbd_con_bin_sem = rt_sem_create("usbd_con_bin_sem", 1, RT_IPC_FLAG_FIFO);
if(usbd_con_bin_sem != RT_NULL)
usbd_consolse_is_init = 1;
}
int32_t usbd_console_put_char(uint8_t _ch)
{
uint8_t isInInterrupt = (SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk) ? 1 : 0;
int delay_time = 0;
if(!usbd_consolse_is_init) return 0;
if(isInInterrupt)
{
delay_time = 0;
}
else
{
delay_time = RT_WAITING_FOREVER;
}
if(rt_sem_take(usbd_con_bin_sem, delay_time) == RT_EOK)
{
if(message_to_print.data_len < MSG_MAX_DATA_SIZE)
{
message_to_print.data[message_to_print.data_len] = (uint8_t)_ch;
message_to_print.data_len++;
}
if(_ch == '\n' || message_to_print.data_len >= MSG_MAX_DATA_SIZE)
{
usbd_send_packet(&message_to_print);
message_to_print.data_len = 0;
}
}
rt_sem_release(usbd_con_bin_sem);
return (uint8_t)_ch;
}
int32_t usbd_console_puts(char *_str)
{
int ret = 0;
while(*_str)
ret |= usbd_console_put_char(*_str++);
return ret;
}
上面说存入的消息满足一定条件时将消息发送至tx_thread中进行发送,这个条件就是上述代码中的
if(_ch == '\n' || message_to_print.data_len >= MSG_MAX_DATA_SIZE)
这句话。
#ifndef _USBD_CONSOLE__H_
#define _USBD_CONSOLE__H_
#include "main.h"
void usbd_console_Init(void);
int32_t usbd_console_put_char(uint8_t ch); /* 输入一个字符到usbd_console缓冲区*/
int32_t usbd_console_puts(char *str); /* 输入一个字符串到usbd_console缓冲区*/
#endif /*_USBD_CONSOLE__H_*/
下面是具体的USB接受和实现的代码
#include "usbd_thread.h"
#include "usbd_consolse.h"
#include <stdio.h>
#include <string.h>
static rt_mq_t tx_mq = RT_NULL;
rt_mq_t rx_mq = RT_NULL;
static uint8_t usbd_thread_is_init = 0;
#define USBD_TX_QUEUE_SIZE 2
#define USBD_RX_QUEUE_SIZE 32
void usbd_uesr_hal_init(void)
{
if(usbd_thread_is_init) return;
tx_mq = rt_mq_create("usbd_send_mq", sizeof(msg_t), USBD_TX_QUEUE_SIZE, RT_IPC_FLAG_FIFO);
rx_mq = rt_mq_create("usbd_recv_mq", sizeof(uint8_t), USBD_RX_QUEUE_SIZE, RT_IPC_FLAG_FIFO);
if((tx_mq != RT_NULL) && (rx_mq != RT_NULL))
usbd_thread_is_init = 1;
}
uint8_t usbd_send_packet(msg_t *_buf)
{
if(_buf == NULL) return RT_ENOMEM;
return rt_mq_send(tx_mq, _buf, sizeof(msg_t));
}
uint8_t usbd_recv_data()
{
int8_t data = -1;
rt_mq_recv(rx_mq, &data, sizeof(uint8_t), 50);
return data;
}
char rt_hw_console_getchar(void)
{
int ch = -1;
ch = usbd_recv_data() & 0XFF;
return (ch);
}
void usbd_rx_thread_entry(void *param)
{
while(1)
{
//do something
rt_thread_delay(200);
}
}
void usbd_tx_thread_entry(void *param)
{
extern uint8_t CDC_Transmit_FS(uint8_t * Buf, uint16_t Len);
msg_t p_msg_queue;
uint8_t sned_buf[MSG_MAX_DATA_SIZE] = {0};
while(1)
{
if(rt_mq_recv(tx_mq, &p_msg_queue, sizeof(msg_t), RT_WAITING_FOREVER) == RT_EOK)
{
memcpy((void *)sned_buf, p_msg_queue.data, p_msg_queue.data_len);
CDC_Transmit_FS(sned_buf, p_msg_queue.data_len);
}
}
}
#ifndef _USBLINK__H_
#define _USBLINK__H_
#include "main.h"
#include "usbd_consolse.h"
#define MSG_MAX_DATA_SIZE 128
typedef struct
{
uint8_t data[MSG_MAX_DATA_SIZE];
uint32_t data_len;
}msg_t;
void usbd_uesr_hal_init(void);
uint8_t usbd_send_packet(msg_t *);
void usbd_rx_thread_entry(void *);
void usbd_tx_thread_entry(void *);
#endif /*usbd_thread.h*/
3、USB注册到RTT控制台
在将USB注册到RTT控制台的过程中,主要分为两部分,依次是发送和接收。 先说一下最简单的的发送。
参考RTT官网,要实现void rt_hw_console_output(const char *str)
函数,具体的实现如下
void rt_hw_console_output(const char *str)
{
rt_enter_critical();
extern int32_t usbd_console_put_char(uint8_t _ch);
char ch;
while(*str != '\0')
{
ch = *str;
if(ch == '\n')
{
ch = '\r';
usbd_console_put_char(ch);
}
usbd_console_put_char(*str);
str++;
}
rt_exit_critical();
/* empty console output */
}
主要的发送实体是函数
usbd_console_put_char(*str);
这里使用局部变量ch
是为了补全\r\n
,方便换行使用。
下面说一下具体的接收函数实现,依旧参考官网给出的例程,需要实现rt_hw_console_getchar()
函数,具体实现如下。
uint8_t usbd_recv_data()
{
int8_t data = -1;
rt_mq_recv(rx_mq, &data, sizeof(uint8_t), 50);
return data;
}
char rt_hw_console_getchar(void)
{
int ch = -1;
ch = usbd_recv_data() & 0XFF;
return (ch);
}
之后需要在usbd_cdc_if.c文件中修改CDC_Receive_FS
函数为如下
static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
{
/* USER CODE BEGIN 6 */
uint8_t i = 0;
uint8_t data = 0;
extern rt_mq_t rx_mq;
USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
USBD_CDC_ReceivePacket(&hUsbDeviceFS);
for(i = 0; i < *Len; i++)
{
data = Buf[i];
rt_mq_send(rx_mq, &data, sizeof(uint8_t));
}
return (USBD_OK);
/* USER CODE END 6 */
}
具体思路就是将接受的函数一个一个的传入接受的消息队列,然后从消息队列中挨个读出。上述的代码实现只满足了最基本的功能,其实可以将接收消息的长度发送到消息队列,方便对数据进行处理。因为前期只是用于调试,所以并没有实现该功能,且RX_THREAD线程里面并未进行任何操作。