本帖最后由 黑皮男 于 2016-11-21 07:41 编辑
上一篇实现了合作式调度器,后续的工程代码只需要建立任务,并进入任务列表中即可。除了LED灯和按键,串口是调试信息输出很好的交互窗口。所以接下来就在NUCLEO-F412上实现串口操作。这里我们实现的是环形发送缓冲方式。本次发一个完整工程。先预告一下,下一弹内容,串口调试组件---仿shell命令行,暂且这么叫吧。
这里讲一下原理。内存的定义是线性的,不可能使环形的,所以这里就要借助软件,做一些特殊处理,需要定义两个变量,一个头指针head,用于定位应该写入的缓存区索引号,另一个是尾指针tail,用来定位当前待发送的字节在缓存区的索引。tail和head之间的区间就是待发送的数据区。当tail和head的值等于缓存大小时,其值就通过软件置零。这样就可以达到循环利用缓存区的目的。图中每个环块代表一个待发送的字节,这是最简单的环形缓存结构。
如果上图中每个环块代表的是一个内存控制块,这个内存控制块包括了指向要发送数据的指针,要发送的数据长度,以及已经发送的数据字节数,那么这就构成一个环形发送队列,数据会逐条发送出去,每条数据的长度可以 不固定,不过这需要内存管理的支持才便于实现,而如果每条数据的缓冲区长度固定,这样我们就可以用一个二维数组来构建一个环形发送缓冲队列,这样的 好处是不需要 内存管理的支持,但每条数据有个最大长度限制。
Com_TypeDef定义了可用的串口号,后续都会通过COM1,COM2这样的形式来引用串口外设
- /*
- *COM1 --- USART2 --- btspp
- *COM2 --- USART3 --- stlink vcom
- *
- */
- typedef enum{
- COM1,
- COM2,
- COMn
- }Com_TypeDef;
复制代码
ComTcb结构定义了串口收发控制接口,目前仅用到发送控制。 ENABLE_DMEM定义了是否使用了内存管理,我这里使用的内存管理是照搬了lwip的内存管理,并做了一些简化,所以暂时不支持系统级别的调用,只能裸机使用。不管使用的是哪种内存管理,效果是一样的,只是用来分配内存缓冲区。
typedef struct{
- //UINT8_T com;
- __IO UINT8_T write_tail; //发送缓存尾指针,当前待发送字节索引
- __IO UINT8_T write_head; //发送缓存头指针,当前待写入字节索引
- __IO UINT8_T read_tail;
- __IO UINT8_T read_head;
- __IO INT16_T count; //当前待发送字节数
- __IO UINT8_T tx_flag; //发送标志
- __IO UINT8_T rx_flag;
- #if ENABLE_DMEM==0
- UINT8_T *write_ptr; //发送缓存入口指针
- UINT8_T *read_ptr;
- INT16_T wbuf_size; //发送缓存区大小,字节为单位
- INT16_T rbuf_size;
- #else
- Com_MemTcb *read_tcb;
- Com_MemTcb *write_tcb; //发送 队列入口指针
- UINT8_T wtcb_size; //发送队列容量
- Q_Typedef q_rx;
- Q_Typedef q_tx; //发送队列
- #endif
- }ComTcb;
复制代码
下面定义了COM1,COM2的收发缓存区,这里是没有用到内存管理的情形。
- UINT8_T com1_txbuffer[COM1_TX_BUFFER_SIZE];
- UINT8_T com1_rxbuffer[COM1_RX_BUFFER_SIZE];
- UINT8_T com2_txbuffer[COM2_TX_BUFFER_SIZE];
- UINT8_T com2_rxbuffer[COM2_RX_BUFFER_SIZE];
复制代码
串口初始化函数。
- void com_init(Com_TypeDef com, UINT32_T baudrate){
- UART_HandleTypeDef *huart;
- huart = &huarts[com];
- com_tcbs[com].write_head = 0;
- com_tcbs[com].write_tail = 0;
- com_tcbs[com].rx_flag = COM_IDLE;
- com_tcbs[com].tx_flag = COM_IDLE;
- #if ENABLE_DMEM==0
- if(com==COM1){
- com_tcbs[com].write_ptr= com1_txbuffer;
- com_tcbs[com].wbuf_size = COM1_TX_BUFFER_SIZE;
- }else if(com==COM2){
- com_tcbs[com].write_ptr = com2_txbuffer;
- com_tcbs[com].wbuf_size= COM2_TX_BUFFER_SIZE;
- }
- #else
- if(com==COM1){
- com_tcbs[com].write_tcb = com1_txmem_tcb;
- com_tcbs[com].wtcb_size = COM1_MEMTCB_SIZE;
- }else if(com==COM2){
- com_tcbs[com].write_tcb = com2_txmem_tcb;
- com_tcbs[com].wtcb_size = COM2_MEMTCB_SIZE;
- }
- #endif
- huart->Instance = com_h[com];
- huart->Init.BaudRate = baudrate;
- huart->Init.HwFlowCtl = UART_HWCONTROL_NONE;
- huart->Init.Mode = UART_MODE_TX_RX;
- huart->Init.Parity = UART_PARITY_NONE;
- huart->Init.StopBits = UART_STOPBITS_1;
- huart->Init.WordLength = UART_WORDLENGTH_8B;
-
- HAL_UART_Init(huart);
- __HAL_USART_ENABLE_IT(huart, USART_IT_RXNE);
- __HAL_USART_CLEAR_FLAG(huart, USART_FLAG_TC);
- }
复制代码
下面是串口的收发函数,由于HAL库中并没有见到l476里面的ll库,自己又不习惯使用HAL中的收发函数,所以这里自己写了这个收发的函数。
- void com_transmitdata8(USART_TypeDef *USARTx, UINT8_T val){
- UINT16_T time_out = 0xFFFF;
- while((USARTx->SR&USART_FLAG_TXE)!=USART_FLAG_TXE&&(time_out--));
- USARTx->DR = val;
- }
- UINT8_T com_receivedata8(USART_TypeDef *USARTx){
- return (USARTx->DR&0xFF);
- }
复制代码
第一种向数据发送队列写数据的方式是没有使用内存管理的,发送缓冲区是一个简单的一维数组。
- INT16_T com_write(Com_TypeDef com,const UINT8_T * ptr, INT16_T count){
- INT16_T res=0;
- if(count==0){
- count = strlen((const char *)ptr);
- }
- if((com_tcbs[com].write_head==com_tcbs[com].write_tail)&&(com_tcbs[com].count!=0)){
- return res;
- }
-
- while(count!=0){
- com_tcbs[com].write_ptr[com_tcbs[com].write_head]=*ptr++;
- count--;
- res++;
- com_tcbs[com].count++;
- com_tcbs[com].write_head++;
- if(com_tcbs[com].write_head>=com_tcbs[com].wbuf_size){
- com_tcbs[com].write_head = 0;
- }
- if(com_tcbs[com].write_head==com_tcbs[com].write_tail){
- break;
- }
- }
- return res;
- }
复制代码
接下来就是启动发送的函数,使能发送数据寄存器空中断。
- void com_flush(Com_TypeDef com){
- UINT16_T index;
- //INT16_T usCount;
- if((com_tcbs[com].count!=0)&&(com_tcbs[com].tx_flag==COM_IDLE)){
- com_tcbs[com].tx_flag = COM_TX_BUSY;
- index = com_tcbs[com].write_tail;
- com_tcbs[com].write_tail++;
- if(com_tcbs[com].write_tail>=com_tcbs[com].wbuf_size){
- com_tcbs[com].write_tail=0;
- }
- com_tcbs[com].count--;
- //__HAL_USART_CLEAR_FLAG(huarts[com], USART_FLAG_TC);
- com_transmitdata8(huarts[com].Instance, com_tcbs[com].write_ptr[index]);
- __HAL_USART_ENABLE_IT(&huarts[com], USART_IT_TXE);
- }
- }
复制代码
第二种数据缓冲队列中每个元素是一个结构体,并有一个指针指向待发送数据。
- INT16_T com_write(Com_TypeDef com, const UINT8_T * ptr, INT16_T count){
- INT16_T res=0;
- if(count==0){
- count = strlen((const char *)ptr);
- }
- if((com_tcbs[com].write_head==com_tcbs[com].write_tail)&&(com_tcbs[com].count!=0)){
- return 0;
- }
- count+=1;
- com_tcbs[com].write_tcb[com_tcbs[com].write_head].mem = dmem_malloc(count);
- if(com_tcbs[com].write_tcb[com_tcbs[com].write_head].mem!=NULL){
- com_tcbs[com].write_tcb[com_tcbs[com].write_head].index = 0;
- memcpy(com_tcbs[com].write_tcb[com_tcbs[com].write_head].mem, ptr, count);
- com_tcbs[com].write_tcb[com_tcbs[com].write_head].num = count-1;
- com_tcbs[com].count++;
- //com_tcbs[com].write_head++;
- res = count-1;
- if(++com_tcbs[com].write_head>=com_tcbs[com].wtcb_size){
- com_tcbs[com].write_head = 0;
- }
- }
- return res;
- }
- void com_flush(Com_TypeDef com){
- //UINT16_T usCount;
- UINT16_T index;
- if((com_tcbs[com].count!=0)&&(com_tcbs[com].tx_flag==COM_IDLE)){
- index = com_tcbs[com].write_tail;
- com_tcbs[com].tx_flag = COM_TX_BUSY;
- com_transmitdata8(huarts[com].Instance, *(com_tcbs[com].write_tcb[index].mem+com_tcbs[com].write_tcb[index].index));
- com_tcbs[com].write_tcb[index].index++;
- __HAL_USART_ENABLE_IT(&huarts[com], USART_IT_TXE);
-
- }
- }
复制代码
最后一个函数是阻塞发送函数,作用是保证当前发送数据都能够发送到发送缓冲队列中,以免发送缓冲队列满而照成的发送失败。
- void com_writeex(Com_TypeDef com, const UINT8_T * ptr, INT16_T count){
- INT16_T temp;
- if(count==0){
- count = strlen((const char *)ptr);
- }
- while(count!=0){
- temp = com_write(com, ptr, count);
- count-=temp;
- ptr+=temp;
- com_flush(com);
- }
- }
复制代码
中断处理函数,只要队列中有数据,就一直发送,知道队列空。
- void com_irq(Com_TypeDef com){
- UINT16_T index;
- UINT16_T count;
-
- if(__HAL_USART_GET_IT_SOURCE(&huarts[com], UART_IT_TXE)){
- #if ENABLE_DMEM == 0
- if(com_tcbs[com].count!=0){
- index = com_tcbs[com].write_tail;
- com_tcbs[com].write_tail++;
- if(com_tcbs[com].write_tail>=com_tcbs[com].wbuf_size){
- com_tcbs[com].write_tail=0;
- }
- com_tcbs[com].count--;
- com_transmitdata8(huarts[com].Instance, com_tcbs[com].write_ptr[index]);
- }else{
- com_tcbs[com].tx_flag=COM_IDLE;
- __HAL_USART_DISABLE_IT(&huarts[com], USART_IT_TXE);
- }
- #else
- if((com_tcbs[com].write_head==com_tcbs[com].write_tail)&&(com_tcbs[com].count==0)){
- return;
- }
- index = com_tcbs[com].write_tail;
- if(com_tcbs[com].write_tcb[index].index==com_tcbs[com].write_tcb[index].num){
- dmem_free(com_tcbs[com].write_tcb[index].mem);
- com_tcbs[com].write_tail++;
- if(com_tcbs[com].write_tail>=com_tcbs[com].wtcb_size){
- com_tcbs[com].write_tail=0;
- }
- com_tcbs[com].count--;
- if(com_tcbs[com].count!=0){
- index = com_tcbs[com].write_tail;
- com_transmitdata8(huarts[com].Instance, *(com_tcbs[com].write_tcb[index].mem+com_tcbs[com].write_tcb[index].index));
- com_tcbs[com].write_tcb[index].index++;
- }else{
- com_tcbs[com].tx_flag = COM_IDLE;
- __HAL_USART_DISABLE_IT(&huarts[com], USART_IT_TXE);
- }
- }else{
- com_transmitdata8(huarts[com].Instance, *(com_tcbs[com].write_tcb[index].mem+com_tcbs[com].write_tcb[index].index));
- com_tcbs[com].write_tcb[index].index++;
- }
- #endif
- return;
- }
- }
复制代码
到此串口环形发送缓冲区就完成了,而接收数据也是类似的,只不过这里没有实现,接收数据需要结合具体的应用来进行构建数据结构。下面是效果,如果按下和释放F412上的User按键,会有相应的数据从串口输出。
0
|
|
|
|
仪124 发表于 2016-11-21 15:05
多谢,自己的写了一半,可以参考一下了
写的结构还是有点乱,不太理想
|
|
|
|
|
机器人工作者 发表于 2016-11-21 17:13
讲的够详细的,非常不错~
多谢支持,希望后续开发的结构更清晰的也会发表
|
|
|
|
|