Porting kfifo based on Linux to STM32 (supporting mutually exclusive access of os)
About kfifo
Kfifo is a First In First Out data structure in the kernel, which uses the data structure of circular queue to realize; it provides a boundless byte stream service, and the most important point is that it uses the parallel lock free programming technology, that is, when it is used in the field with only one queue in thread and one queue out thread, the two threads can operate concurrently without any need. The locking behavior can ensure the thread safety of kfifo.
What is ring buffer? Please see my previous article.
Explain
I will not introduce the related concepts of kfifo. If you are interested, you can see his related documents. I only transplant and rewrite the implementation process to the applicable stm32 development board, and rename it according to my personal habits. Ringbuff - > means ring buffer.
RingBuff_t
The structure member variable of the ring buffer. See the note for the specific meaning.
buffer: the cache used to store data
Size: size of buffer space
in, out: forms a circular queue with buffer. in points to the head of the buffer Squadron, and out points to the tail of the buffer squadron
typedef struct ringbuff { uint8_t *buffer; /* Data region */ uint32_t size; /* Ring buffer size */ uint32_t in; /* Data entry pointer (in% size) */ uint32_t out; /* Data out% size */ #if USE_MUTEX MUTEX_T *mutex; /* Supporting rtos mutual exclusion */ #endif }RingBuff_t ;
Create_RingBuff
Create a ring buffer. The size of the ring buffer should be 2^n bytes in order to adapt to the subsequent efficient operation of buffer queuing.
If it is not this size, the system defaults to clipping to correspond to the buffer bytes.
Of course, it can also be optimized, but I haven't done so far. The idea is as follows: if the system supports dynamic memory allocation, align up to avoid wasting memory space. Otherwise, align down according to my default. The larger the memory, the more memory leaks will be caused by alignment. The function used for alignment is round out of two. If the system supports mutex, a mutex will also be created for mutex access to prevent data loss caused by simultaneous use of multiple threads.
/************************************************************ * @brief Create_RingBuff * @param rb: Ring buffer handle * buffer: Data area of ring buffer * size: Size of ring buffer, buffer size should be 2^n * @return err_t: ERR_OK Indicates creation success, others indicate failure * @author jiejie * @github https://github.com/jiejieTop * @date 2018-xx-xx * @version v1.0 * @note Used to create a ring buffer ***********************************************************/ err_t Create_RingBuff(RingBuff_t* rb, uint8_t *buffer, uint32_t size ) { if((rb == NULL)||(buffer == NULL)||(size == 0)) { PRINT_ERR("data is null!"); return ERR_NULL; } PRINT_DEBUG("ringbuff size is %d!",size); /* Buffer size must be 2^n bytes, system will cast, Otherwise, the pointer may access the illegal address. The larger the space is, the more memory will be lost during strong rotation */ if(size&(size - 1)) { size = roundup_pow_of_two(size); PRINT_DEBUG("change ringbuff size is %d!",size); } rb->buffer = buffer; rb->size = size; rb->in = rb->out = 0; #if USE_MUTEX /* Failed to create semaphore */ if(!create_mutex(rb->mutex)) { PRINT_ERR("create mutex fail!"); ASSERT(ASSERT_ERR); return ERR_NOK; } #endif PRINT_DEBUG("create ringBuff ok!"); return ERR_OK; }
roundup_pow_of_two
/************************************************************ * @brief roundup_pow_of_two * @param size: Length of data passed in * @return size: Return the data length after processing * @author jiejie * @github https://github.com/jiejieTop * @date 2018-xx-xx * @version v1.0 * @note Used to process data so that the data length must be 2^n * If not, convert and discard the extra parts, such as * roundup_pow_of_two(66) -> Return to 64 ***********************************************************/ static unsigned long roundup_pow_of_two(unsigned long x) { return (1 << (fls(x-1)-1)); //Alignment down //Return (1ul < FLS (x - 1)); / / align up and use dynamic memory. }
Delete_RingBuff
Delete a ring buffer. After deletion, the real storage address of the buffer will not be changed (currently I use a custom array as the buffer), but after deletion, the buffer cannot be read or written. And if os is supported, the created mutex will be deleted.
/************************************************************ * @brief Delete_RingBuff * @param rb: Ring buffer handle * @return err_t: ERR_OK Success, other failures * @author jiejie * @github https://github.com/jiejieTop * @date 2018-xx-xx * @version v1.0 * @note Delete a ring buffer ***********************************************************/ err_t Delete_RingBuff(RingBuff_t *rb) { if(rb == NULL) { PRINT_ERR("ringbuff is null!"); return ERR_NULL; } rb->buffer = NULL; rb->size = 0; rb->in = rb->out = 0; #if USE_MUTEX if(!deleta_mutex(rb->mutex)) { PRINT_DEBUG("deleta mutex is fail!"); return ERR_NOK; } #endif return ERR_OK; }
Write_RingBuff
Writes the specified data to the ring buffer, and supports thread exclusive access. The length of the data that the user wants to write to the buffer is not necessarily the length of the actual queue, but also to see whether the return value is consistent with the length required by the user when it is finished.~
This function is very interesting and efficient. Copy the data in the specified area to the specified buffer. See the comments for the process.
/************************************************************ * @brief Write_RingBuff * @param rb:Ring buffer handle * @param wbuff:Data start address written * @param len:Length of data written (bytes) * @return len:Length of data actually written (bytes) * @author jiejie * @github https://github.com/jiejieTop * @date 2018-xx-xx * @version v1.0 * @note This function copies len byte length data from buff space to rb Free space in the ring buffer. ***********************************************************/ uint32_t Write_RingBuff(RingBuff_t *rb, uint8_t *wbuff, uint32_t len) { uint32_t l; #if USE_MUTEX /* Request mutex, and access to ringbuff can only be done successfully */ if(!request_mutex(rb->mutex)) { PRINT_DEBUG("request mutex fail!"); return 0; } else /* Get mutex succeeded */ { #endif len = min(len, rb->size - rb->in + rb->out); /* Copy of the first part: write data from the ring buffer to the last address of the buffer */ l = min(len, rb->size - (rb->in & (rb->size - 1))); memcpy(rb->buffer + (rb->in & (rb->size - 1)), wbuff, l); /* Write the rest of the buffer header if it overflows If it doesn't overflow, the code is invalid. */ memcpy(rb->buffer, wbuff + l, len - l); rb->in += len; PRINT_DEBUG("write ringBuff len is %d!",len); #if USE_MUTEX } /* Release mutex */ release_mutex(rb->mutex); #endif return len; }
Read_RingBuff
Read the buffer data to the specified area, and the user specifies the read length. The length that the user wants to read is not necessarily the real read length. When the reading is completed, it is also necessary to see whether the return value is consistent with the length required by the user ~ it also supports multi-threaded exclusive access.
It is also an efficient operation of buffer queuing. See the code comments for the process.
/************************************************************ * @brief Read_RingBuff * @param rb:Ring buffer handle * @param wbuff:Read the starting address of data saving * @param len:Length of data to be read (bytes) * @return len:Length of data actually read (bytes) * @author jiejie * @github https://github.com/jiejieTop * @date 2018-xx-xx * @version v1.0 * @note This function copies len bytes from the data area in the rb ring buffer Length of data into the rbuff space. ***********************************************************/ uint32_t Read_RingBuff(RingBuff_t *rb, uint8_t *rbuff, uint32_t len) { uint32_t l; #if USE_MUTEX /* Request mutex, and access to ringbuff can only be done successfully */ if(!request_mutex(rb->mutex)) { PRINT_DEBUG("request mutex fail!"); return 0; } else { #endif len = min(len, rb->in - rb->out); /* Copy of the first part: read data from the ring buffer to the last buffer */ l = min(len, rb->size - (rb->out & (rb->size - 1))); memcpy(rbuff, rb->buffer + (rb->out & (rb->size - 1)), l); /* Read the rest of the buffer header if it overflows If it doesn't overflow, the code is invalid. */ memcpy(rbuff + l, rb->buffer, len - l); rb->out += len; PRINT_DEBUG("read ringBuff len is %d!",len); #if USE_MUTEX } /* Release mutex */ release_mutex(rb->mutex); #endif return len; }
Get buffer information
It's easier to see how much data the buffer can read and write.
/************************************************************ * @brief CanRead_RingBuff * @param rb:Ring buffer handle * @return uint32:Readable data length 0 / len * @author jiejie * @github https://github.com/jiejieTop * @date 2018-xx-xx * @version v1.0 * @note Readable data length ***********************************************************/ uint32_t CanRead_RingBuff(RingBuff_t *rb) { if(NULL == rb) { PRINT_ERR("ringbuff is null!"); return 0; } if(rb->in == rb->out) return 0; if(rb->in > rb->out) return (rb->in - rb->out); return (rb->size - (rb->out - rb->in)); } /************************************************************ * @brief CanRead_RingBuff * @param rb:Ring buffer handle * @return uint32:Writable data length 0 / len * @author jiejie * @github https://github.com/jiejieTop * @date 2018-xx-xx * @version v1.0 * @note Writable data length ***********************************************************/ uint32_t CanWrite_RingBuff(RingBuff_t *rb) { if(NULL == rb) { PRINT_ERR("ringbuff is null!"); return 0; } return (rb->size - CanRead_RingBuff(rb)); }
Incidental
The code here is for testing. I write it casually.
RingBuff_t ringbuff_handle; uint8_t rb[64]; uint8_t res[64]; Create_RingBuff(&ringbuff_handle, rb, sizeof(rb)); Write_RingBuff(&ringbuff_handle, res, datapack.data_length); PRINT_DEBUG("CanRead_RingBuff = %d!",CanRead_RingBuff(&ringbuff_handle)); PRINT_DEBUG("CanWrite_RingBuff = %d!",CanWrite_RingBuff(&ringbuff_handle)); Read_RingBuff(&ringbuff_handle, res, datapack.data_length);
Support mutex operations of multiple os
This mimics the file system's mutex operation
#if USE_MUTEX #Define mutex? Timeout 1000 / * timeout*/ #Define mutex? T mutex? T / * mutex control block*/ #endif /*********************************** mutex **************************************************/ #if USE_MUTEX /************************************************************ * @brief create_mutex * @param mutex:Create semaphore handle * @return Creation success is 1, 0 is unsuccessful. * @author jiejie * @github https://github.com/jiejieTop * @date 2018-xx-xx * @version v1.0 * @note Create a mutex. Users can use ringbuff for mutex in os. * Supported os include rtt, win32, ucos, FreeRTOS, LiteOS ***********************************************************/ static err_t create_mutex(MUTEX_T *mutex) { err_t ret = 0; // *mutex = rt_mutex_create("test_mux",RT_IPC_FLAG_PRIO); /* rtt */ // ret = (err_t)(*mutex != RT_NULL); // *mutex = CreateMutex(NULL, FALSE, NULL); /* Win32 */ // ret = (err_t)(*mutex != INVALID_HANDLE_VALUE); // *mutex = OSMutexCreate(0, &err); /* uC/OS-II */ // ret = (err_t)(err == OS_NO_ERR); // *mutex = xSemaphoreCreateMutex(); /* FreeRTOS */ // ret = (err_t)(*mutex != NULL); // ret = LOS_MuxCreate(&mutex); /* LiteOS */ // ret = (err_t)(ret != LOS_OK); return ret; } /************************************************************ * @brief deleta_mutex * @param mutex:Mutex handle * @return NULL * @author jiejie * @github https://github.com/jiejieTop * @date 2018-xx-xx * @version v1.0 * @note Delete a mutex. Supported os include rtt, win32, ucos, FreeRTOS and LiteOS. ***********************************************************/ static err_t deleta_mutex(MUTEX_T *mutex) { err_t ret; // ret = rt_mutex_delete(mutex); /* rtt */ // ret = CloseHandle(mutex); /* Win32 */ // OSMutexDel(mutex, OS_DEL_ALWAYS, &err); /* uC/OS-II */ // ret = (err_t)(err == OS_NO_ERR); // vSemaphoreDelete(mutex); /* FreeRTOS */ // ret = 1; // ret = LOS_MuxDelete(&mutex); /* LiteOS */ // ret = (err_t)(ret != LOS_OK); return ret; } /************************************************************ * @brief request_mutex * @param mutex:Mutex handle * @return NULL * @author jiejie * @github https://github.com/jiejieTop * @date 2018-xx-xx * @version v1.0 * @note Request a mutex, and the thread that gets the mutex will be allowed to access the buffer. * Supported os include rtt, win32, ucos, FreeRTOS, LiteOS ***********************************************************/ static err_t request_mutex(MUTEX_T *mutex) { err_t ret; // ret = (err_t)(rt_mutex_take(mutex, MUTEX_TIMEOUT) == RT_EOK);/* rtt */ // ret = (err_t)(WaitForSingleObject(mutex, MUTEX_TIMEOUT) == WAIT_OBJECT_0); /* Win32 */ // OSMutexPend(mutex, MUTEX_TIMEOUT, &err)); /* uC/OS-II */ // ret = (err_t)(err == OS_NO_ERR); // ret = (err_t)(xSemaphoreTake(mutex, MUTEX_TIMEOUT) == pdTRUE); /* FreeRTOS */ // ret = (err_t)(LOS_MuxPend(mutex,MUTEX_TIMEOUT) == LOS_OK); /* LiteOS */ return ret; } /************************************************************ * @brief release_mutex * @param mutex:Mutex handle * @return NULL * @author jiejie * @github https://github.com/jiejieTop * @date 2018-xx-xx * @version v1.0 * @note Release the mutex. When the thread finishes using the resource, it must release the mutex. * Supported os include rtt, win32, ucos, FreeRTOS, LiteOS ***********************************************************/ static void release_mutex(MUTEX_T *mutex) { // rt_mutex_release(mutex);/* rtt */ // ReleaseMutex(mutex); /* Win32 */ // OSMutexPost(mutex); /* uC/OS-II */ // xSemaphoreGive(mutex); /* FreeRTOS */ // LOS_MuxPost(mutex); /* LiteOS */ } #endif /*********************************** mutex **************************************************/
debug.h
Finally, send a simple source code for debug ging, because many times the previous article will call
PRINT_ERR
PRINT_DEBUG
#ifndef _DEBUG_H #define _DEBUG_H /************************************************************ * @brief debug.h * @author jiejie * @github https://github.com/jiejieTop * @date 2018-xx-xx * @version v1.0 * @note This file is used to print log information ***********************************************************/ /** * @name Debug print * @{ */ #Define print? Debug? Enable 1 / * print debug information*/ #Define print? Err? Enable 1 / * print error message*/ #Define print "info" enable 0 / * print personal information*/ #if PRINT_DEBUG_ENABLE #define PRINT_DEBUG(fmt, args...) do{(printf("\n[DEBUG] >> "), printf(fmt, ##args));}while(0) #else #define PRINT_DEBUG(fmt, args...) #endif #if PRINT_ERR_ENABLE #define PRINT_ERR(fmt, args...) do{(printf("\n[ERR] >> "), printf(fmt, ##args));}while(0) #else #define PRINT_ERR(fmt, args...) #endif #if PRINT_INFO_ENABLE #define PRINT_INFO(fmt, args...) do{(printf("\n[INFO] >> "), printf(fmt, ##args));}while(0) #else #define PRINT_INFO(fmt, args...) #endif /**@} */ //Calling different stdint.h files for different compilers #if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__) #include <stdint.h> #endif /* Assert */ #define AssertCalled(char,int) printf("\nError:%s,%d\r\n",char,int) #define ASSERT(x) if((x)==0) AssertCalled(__FILE__,__LINE__) typedef enum { ASSERT_ERR = 0, /* error */ ASSERT_SUCCESS = !ASSERT_ERR /* Correct */ } Assert_ErrorStatus; typedef enum { FALSE = 0, /* false */ TRUE = !FALSE /* really */ }ResultStatus; #endif /* __DEBUG_H */
Like to pay attention to me!
Relevant codes can be obtained in the public account background.
Welcome to the public account of "IoT development"