stm32cubemx生成工程
- 本次使用STM32F103C8,串口1演示空闲中断+DMA接收不定长数据,发送到消息队列,从任务中读取消息,通过printf打印。
- 本文只留干货,关于STM32系统和串口配置不做讲解,仅讲解FreeRTOS配置部分,串口和DMA使用的是LL库。
stm32cubemx配置freertos工程
RTOS配置只做如下修改,内核版本选择V1,内存分配选择动态,将堆栈尺寸改大。
添加队列和任务,将队列长度设置为3,每项大小设置为uint32_t(此处设置为uint32_t原因是:32位单片机的指针大小为4字节),为什么设置为uint32_t,后文讲解。
osMessagePut,osMessageGet函数
osMessagePut
使用inHandlerMode()判断当前是否在中断中,这样一来,在中断和任务中,我们都只用调用 osMessagePut函数 即可 ,osMessageGet函数也是如此。
osStatus osMessagePut (osMessageQId queue_id, uint32_t info, uint32_t millisec)
{
portBASE_TYPE taskWoken = pdFALSE;
TickType_t ticks;
ticks = millisec / portTICK_PERIOD_MS;
if (ticks == 0) {
ticks = 1;
}
if (inHandlerMode()) {
if (xQueueSendFromISR(queue_id, &info, &taskWoken) != pdTRUE) {
return osErrorOS;
}
portEND_SWITCHING_ISR(taskWoken);
}
else {
if (xQueueSend(queue_id, &info, ticks) != pdTRUE) {
return osErrorOS;
}
}
return osOK;
}
osMessageGet
osEvent osMessageGet (osMessageQId queue_id, uint32_t millisec)
{
portBASE_TYPE taskWoken;
TickType_t ticks;
osEvent event;
event.def.message_id = queue_id;
event.value.v = 0;
if (queue_id == NULL) {
event.status = osErrorParameter;
return event;
}
taskWoken = pdFALSE;
ticks = 0;
if (millisec == osWaitForever) {
ticks = portMAX_DELAY;
}
else if (millisec != 0) {
ticks = millisec / portTICK_PERIOD_MS;
if (ticks == 0) {
ticks = 1;
}
}
if (inHandlerMode()) {
if (xQueueReceiveFromISR(queue_id, &event.value.v, &taskWoken) == pdTRUE) {
/* We have mail */
event.status = osEventMessage;
}
else {
event.status = osOK;
}
portEND_SWITCHING_ISR(taskWoken);
}
else {
if (xQueueReceive(queue_id, &event.value.v, ticks) == pdTRUE) {
/* We have mail */
event.status = osEventMessage;
}
else {
event.status = (ticks == 0) ? osOK : osEventTimeout;
}
}
return event;
}
如果是先学习了FreeRTOS,再来使用osMessageGet这个函数的时候,就会有个坑,这个函数返回的是event,由osEvent 定义,如下
typedef struct {
osStatus status; ///< status code: event or error information
union {
uint32_t v; ///< message as 32-bit value
void *p; ///< message or mail as void pointer
int32_t signals; ///< signal flags
} value; ///< event value
union {
osMailQId mail_id; ///< mail id obtained by \ref osMailCreate
osMessageQId message_id; ///< message id obtained by \ref osMessageCreate
} def; ///< event definition
} osEvent;
osMessageGet中调用xQueueReceiveFromISR和xQueueReceive,其中传入的参数为 &event.value.v,在osEvent定义的结构体中,它是一个共用体,可以作为uint32_t/int32_t(数值),或者void *(指针)使用。osMessagePut如果只是传入一个数值使用并没有问题,但在freertso是以队列项传入的,而在cmsis-rtos中传入消息队列的是一个4字节数据(可以是指针,可以是一个值),所以在调用osMessageGet返回后值中,能够使用的就是一个指针,或者为一个4字节数值。stm32cubemx虽然使用freertos,但是也做了cmsis-rtos,这就是我为什么在stm32cubemx配置队列的时候,将项的大小选择了uint32_t(4字节),目的就是把队列项作为一个指针使用。
应用
参考CMSIS-RTOS V1.03https://www.keil.com/pack/doc/CMSIS/RTOS/html/index.html
打开stm32cubemx创建的工程,在main.h定以我的串口消息数据包类型
typedef struct{
uint8_t buff[128];
uint16_t len;
}USART_Msg_Def;
使用cmsis-rtos中的内存申请函数,为我的消息缓冲区申请内存。
osPoolId USART_Msg_pool;//定义我消息缓冲区的内存池句柄
uint8_t Rx1_buff[128];//串口1的DMA接受缓冲区
在队列创建代码的后面加入内存申请代码
/* definition and creation of myQueue01 */
osMessageQDef(myQueue01, 3, uint32_t);
myQueue01Handle = osMessageCreate(osMessageQ(myQueue01), NULL);
/* USER CODE BEGIN RTOS_QUEUES */
/* add queues, ... */
osPoolDef(USART_Msg_pool, 3, USART_Msg_Def);
USART_Msg_pool = osPoolCreate(osPool(USART_Msg_pool));
/* USER CODE END RTOS_QUEUES */
在StartDefaultTask加入消息读取函数和配置DMA+空闲中断接收
/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void const * argument)
{
/* USER CODE BEGIN StartDefaultTask */
USART_Msg_Def *message;
osEvent evt;
// 接收DMA配置
LL_DMA_SetPeriphAddress(DMA1, LL_DMA_CHANNEL_5, LL_USART_DMA_GetRegAddr(USART1));// 配置外设地址
LL_DMA_SetMemoryAddress(DMA1, LL_DMA_CHANNEL_5, (uint32_t)Rx1_buff);// 配置缓冲区地址
LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_5, 128);// 配置缓冲区大小
// LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_5);// 开启DMA传输完成中断
// LL_DMA_EnableIT_TE(DMA1, LL_DMA_CHANNEL_5);// 开启DMA传输错误中断
NVIC_DisableIRQ(DMA1_Channel5_IRQn);
LL_USART_EnableDMAReq_RX(USART1);// 使能串口的DMA接收功能
LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_5); // 使能DMA通道
LL_USART_EnableIT_IDLE(USART1);// 开启串口1中断
/* Infinite loop */
for(;;)
{
evt = osMessageGet(myQueue01Handle,0);
if (evt.status == osEventMessage)
{
message = (USART_Msg_Def *)evt.value.p;
printf("队列读取到消息:(%d)%s",message->len,message->buff);
osPoolFree(USART_Msg_pool, message);//释放内存
}
osDelay(1);
}
/* USER CODE END StartDefaultTask */
}
/* USER CODE END Header_StartTask02 */
void StartTask02(void const * argument)
{
/* USER CODE BEGIN StartTask02 */
/* Infinite loop */
for(;;)
{
//任务二中就加入一个简单的闪灯代码吧
LL_GPIO_TogglePin(LED_GPIO_Port,LED_Pin);
osDelay(500);
}
/* USER CODE END StartTask02 */
}
中断加入消息发送函数
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
USART_Msg_Def *message;
if (LL_USART_IsActiveFlag_IDLE(USART1) && LL_USART_IsEnabledIT_IDLE(USART1))
{
LL_USART_ClearFlag_IDLE(USART1);// 清除空闲中断标志位
LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_5);// 关闭DMA通道
LL_USART_ReceiveData8(USART1);// 读取一下接收寄存器
message = (USART_Msg_Def*)osPoolAlloc(USART_Msg_pool);//申请内存
message->len = 128 - LL_DMA_GetDataLength(DMA1, LL_DMA_CHANNEL_5);// 计算接收长度
memcpy(message->buff,Rx1_buff,message->len);//数据拷贝
osMessagePut(myQueue01Handle, (uint32_t)message, osWaitForever);//写入队列
printf("队列写入到消息:(%d)%s",message->len,message->buff);
// 重新设置数据长度并打开DMA,使DMA从头开始接收
LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_5, 128);
LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_5);
}
/* USER CODE END USART1_IRQn 0 */
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
测试
第一次我发送了hello word,下面勾选上了加回车换行,总字节为13,从任务函数中读取出了。
第二次我发送了This is a test program,勾选上了加回车换行,总字节为24,从任务函数中读取出了。
第三次我发送了hello word,下面勾选上了加回车换行,总字节为13,从任务函数中读取出了,但是在打印的时候“hello word”的后面却多了“t program”,这是为什么呢,留给爱学习的你去思考(温馨提示:这与内存管理有关)。