代码风格和规范

关于C语言的这个代码风格和规范的问题,其实在很早就一直困扰着我。没有系统性的归纳过。今天蹭着这机会就来系统地梳理一下。

首先是关于这个头文件的问题:

网上关于这个头文件的说法是,头文件好不要包含其他的头文件,如果是源文件需要的话,直接再源文件中进行包含,而不是在头文件中包含,如果是头文件中,需要使用到其他的头文件中的结构体的定义等的话,则就只是包含相关的头文件。然后在源文件中,最好是只使用需要用到的模块的头文件。不要用一个includes.h(但是我还是感觉这样的做法比较方便。。。)

关于变量命名的问题:

这里我应该都是比较规范地进行命名的,但是还是需要注意一点就是不用的变量或者可以省略的变量尽量都将其删除了。

关于函数命名的问题:

我看网上进行函数命名的时候有下划线的方式也有大小写的方式,这里我还是统一用大小写的方式,但是如果是相关的名词全是大写的话,我就再用下划线区分一下(例如在写这种LED_Init)其他就没有什么大问题了。

代码规范只是一个需要长期培养的一个习惯,只要平时注意一下,有些问题就总结一下。

模块化的定义

现在出现了一个新的层级,就是模块。

以前的工程里面我都只是用的是一层的驱动,就是hardware。但是现在出现了新的一个层级,里面是需要继续调用hardware的函数,这样就让我的工程组织起来稍微又要麻烦一点。今天的事故主要出现在这个modules上,我一直想把这个BC26依旧当作这个hardware层级来处理,但是发现,无论如何都是要包含一大堆的底层的函数(例如usart等),想要解决这个问题及必须要引入新的一层的定义来处理这个事情。

现在有一点小插曲,我需要先调试一下板卡,因为忘记带上足够的杜邦线,现在只能够先用一下J-Link来调试。

首先一来就遇到了一个问题,应该是由于这个CubeMX更新造成的,生成的工程有问题:The Code is successily generaed under: D我的文档5大创(202105-codeKEILForTestTest Projct language : C but MDK-ARMV5.32project generation have a poblem。

网上说这个问题是因为路径中包含了一些非法字符造成的,那应该问题不大。最后查明原因,就是因为这个问题导致的。

然后又遇到一个问题是说:No Crotex-M Device found in JTAG chain Please check the JTAG cable and the…,这是由于没有选对J-Link的接口(在板子上用的串行接口SW,而不是JTAG)因此,在选项中选择切换过来后就对了。

J-Link Settings

串口驱动编写

今天的任务是把串口调通,代码看懂(20220128),串口这个东西,也是属于一直在模模糊糊用,但是一直没有搞明白的东西。今天看了一下他们厂家编写的串口的代码,遇到了一些看不懂的东西。

首先,是这个寄存器的使用的问题。我看到源代码应该是直接定义使用了一个全局变量:

1
u16 USART_RX_STA = 0; //接收状态标记

在后面的操作中,也是直接对这个定义的全局变量进行读值和写值的操作,这样是直接对相应的寄存器操作了吗?有一点迷惑,等一段时间再来看这个问题吧。:cry:

整体思路

先写一下整体的思路,首先是通过CubeMX建立一个有串口初始化的空工程(例如初始化串口2,这个串口也是我们后面用来和BC26模块进行通信的串口),然后是开启串口的接受中断(在串口接受到信息后,向主机再发送一个相同的信息)这样就可以开启串口的使用了;在完成前述的初级功能之后,在进行printf函数的重写,通过串口1,能够直接在电脑上打印相应的字符串,说干就干。

这个串口是直接就调通了,现在可以直接通过HAL_UART_TRANSMIT直接进行传输了,这种是通过阻塞的模式进行的。但是好像我看了一下正点原子的程序都是通过中断的方式进行的,这样就涉及到一个问题,就是缓存区的问题,如果是阻塞模式,就直接调用函数就行,而不需要先定义一个全局的缓存区。

这里我的的思路是,还是像HAL库函数那样,先顶一个指针,当在主函数中用到相应的缓存区再声明,模块化函数还是最好不要出现全局变量(如果需要,就使用一个静态的全局变量),不知道这样会不会出现什么问题。

现在是可以通过使用这个串口的阻塞模式进行任意的方式的收发了,但是现在还没有实现中断模式,现在看了一下,原来的工程里面也是进行的发送是用阻塞模式(使用transmit或者printf):

1
2
3
//UART发送信息的方式
HAL_UART_Transmit(&UART_Handler,(uint8_t*)USART_RX_BUF,len,1000);
printf("您发送的消息为:\r\n");

在UART进行接受的时候,是用的是中断的方式,因为不知道何时会发送消息过来。于是,在UART的初始化中需要加入下面的中断初始化代码。

1
2
HAL_NVIC_EnableIRQ(USART_IRQn);			   // 使能USART1中断通道
HAL_NVIC_SetPriority(USART_IRQn,3,0); // 抢占优先级3,子优先级0

刚才看懂了源代码的意思,它定义一个最大接受的数据缓存的大小,如果说大小超过了,就需要进行重新的输入。这里默认是是一次发送一行数据,在接受完一行数据后就对数据进行保持和处理。

但是原来的代码也有一个问题,就是它是在中断里面操作了数据的处理,然后又回到主函数中进行打印的操作,感觉有一点多余,反正在中断中都已经使用了大量的代码段了,为什么加一个打印还说效率不高呢,这不是捡芝麻丢西瓜吗,代码都不太好阅读了。

总体需求分析

现在还是应该在写一个模块之前把这个整体的架构和思路想一遍,想清楚再写效率比较高,这里已经是搞清楚了收发的原理。在HAL处理了一个数据之后,需要把这个数据输出到一个缓冲区之中,然后再把接下来送进来的数据依次加进去,在读取到行结束标志(回车)的时候,才停下来。

编程思路

总体的想法是这个HAL的buffer要保留下来,因为这个是每次输入进来都要用到,关键问题是每次接受完一行数据之后是应该在哪里处理,原本是通过一个全局变量判断其状态,在其状态显示已经接受完一行数据之后,在外部处理。

我现在的想法是这个一行数据的buffer就不用这个uart里面的数组了,而是在外面的程序中定义一个数组,然后在uart里面定义一个指针,在初始化的时候就可以把这个指针指向外面定义的那一个空间,对其进行处理。这样的好处是需要的时候在定义一片空间,就不会造成资源的浪费。

关于处理中断的这个操作,我觉得还是可以在中断外面进行操作,虽然有一点打脸,但是想了一下还是这样的操作会比较明晰。

接口定义

外部接口:

  • (全局变量)u8 UARTx_RX_STA:中断接收的状态,1:当前接收一个数据完成,0:当前接收数据还未完成。当接受完成之后,能否产生一个事件(?),或者说是轮询方式去查询(外部查询的方式有需要涉及到一个超时的问题)?并且在标志完成的时候,应该需要防止外面继续产生中断(关闭),当处理完这个之后在处理下一行代码。
  • (初始化函数)void USARTx_UART_Init(u8* uartx_rx_buffer):串口的初始化定义,需要定义一篇buffer,然后再传参进去;
  • (全局变量)u8 uartx_rx_buffer[MAX_RX_NUMBER]:这个接收的buffer我放弃了,因为在中断中要对这个进行操作,所以说必须要用全局变量。

其他的操作都是在内部操作了,包括中断操作的编写都应该在内部完成。当初始化之后,就相当于是只需要读取STA的状态就能够判断是否需要进行读出的操作。

编程实现

注意点:

  • 在每次接收到一行的数据之后,会先后到来一个回车键(0x0d)和换行键(0x0a),因此,判断一行读进来的标志就是这两个键读了进来没有。

接口实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 接口定义
// 串口操作句柄
extern UART_HandleTypeDef huart1;
extern UART_HandleTypeDef huart2;
// 下面这两个全局变量是用来判断中断接收的状态的变量
extern u8 uart1_rx_flag;
extern u8 uart2_rx_flag;
// 下面两个全局变量是用来判断中断接收的数据的个数的变量
extern u8 uart1_rx_count;
extern u8 uart2_rx_count;
// 下面的全局变量是存放接收数据的缓存,其会接收一行缓存(以回车为接收结束的标志),前面的宏定义了最大的接收的数量。
extern u8 uart1_rx_buffer[MAX_RX_NUMBER]; // 接受的一行数据后的buffer(以回车作为结束)
extern u8 uart2_rx_buffer[MAX_RX_NUMBER]; // 接受的一行数据后的buffer(以回车作为结束)
// 下面两个函数是串口的初始化函数,在初始化中,定义和使能了串口的中断,因此,串口都是通过中断的方式工作的,接口也就是上方定义的全局变量。
void USART1_UART_Init(void);
void USART2_UART_Init(void);

逻辑实现(在回调函数中)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
if (huart->Instance == USART1) // 如果是串口1
{
static u8 return_key_flag = FALSE;
if (uart1_rx_flag == FALSE) // 接收未完成
{
if (return_key_flag == TRUE) // 已经接收到0x0d(回车键)
{
// 是否又接收到了换行键
if (uart1_hal_rx_buffer == 0x0a)
{
// 一次接收完毕
uart1_rx_flag = TRUE;
}
else // 发生错误,重新接收
{
uart1_rx_count = 0;
}
return_key_flag = FALSE;
}
else // 还未接收到0x0d(回车键)
{
if (uart1_hal_rx_buffer == 0x0d)
{
return_key_flag = TRUE;
}
else
{
uart1_rx_buffer[uart1_rx_count] = uart1_hal_rx_buffer;
uart1_rx_count++;
// 如果超过最大值,缓存不够,重新开始
if (uart1_rx_count > (MAX_RX_NUMBER - 1))
{
uart1_rx_count = 0;
}
}
}
}
}

遇到的一些问题和改善方式

接口定义没有变,但是要注意接口使用的时候需要手动地将其flag置位,这一点在源代码中有说,然后就是,新定义了两个变量用来存放count,便于自动化清零操作。

总结

这次整理代码废了很大的力气,不过也有一些小小的收获:

  • 在写一个模块和代码之前,先想好需求,接口的定义等内容,然后再开始写代码这样的效率会高很多。
  • 在涉及到中断程序的时候,与外部的接口不可避免会用到全局变量。
  • 在涉及整体的程序框架的时候,可以使用一下状态机的编程思路。