糕工的C语言开发日志 半月刊(日更版) 第四期2024/8上

一、专业英语

1.英语

limit界限
scheduler 调度器
mutex互斥锁
binary 二进制的 Semaphore 信号量 ,二值信号量(只有0/1)
critical sections 临界区
race condition 条件竞争
woken 唤醒
notified 公告
Peripheral 环形
Encapsulation 封装,Inheritance 继承和 Polymorphism 多态性。
local 局部的
global 全局的
handled 处理
released 释放
pressed 按下

mount 挂载(常用于文件系统)
mkfs   格式化文件系统
format 格式化
information 信息/info


cache 极高速缓存

TCM = Tightly Coupled Memory,是一种高速缓存,
据说是被直接集成在CPU芯片中。

DS有两种TCM,分别是ITCM(Instruction TCM)和DTCM(Data TCM)

Memory Management Unit的缩写,中文名是内存管理单元
IRQ Interupt ReQuest 中断请求

font_lib 字库
chip 芯片
kernel 内核
aligned 对齐 (__attribute__ ((aligned (4)));)

二、汇编、中断、寄存器

1.汇编简单应用

汇编指令操作的是地址,譬如你有一个变量a,要实现a=5;如下

LDR R0,a(如果有偏移量这里要用=a+offset)   
加载a的地址,相当于 r0=&a;

MOV R1,#5                                                
将5保存在R1,相当于 r1=5;

STR R1,[R0]                        
将R1的值给到R0地址的指向位置上,相当于 *r0 = r1;

2.bx跳出函数

        bx lr 不是直接跳转到lr,而是先根据标准栈帧规定,
 

低地址    ---     高地址
r0-r3 r12 lr pc xpsr,

        将所有寄存器的值从栈弹出,然后再访问弹出的LR,这也是实现pendsv切换任务的基础

3.pendsv中断主动触发
        

        中断有个触发标志位,可以通过手动改变该寄存器的对应位,来触发中断,这也是主动切换任务的核心内容和方式。

4.临界区与资源保护


        临界区进入和退出保护的实质就是:进入时保存了中断状态寄存器,而后关闭中断,最后退出时候恢复进入临界区时保存的寄存器状态。

三、C/C++语法

1.const后置
       

         uint16_t Shape::distanceFrom(Shape const * other) const ,末尾的const代表这是一个只读函数,不会修改调用者的内容。

Shape::Shape(std::int16_t x0, std::int16_t y0)
: x(x0),
  y(y0)
{

}

        和在{}内将x0,y0赋值初始化是一样的,但是更推荐第一种用法。

2.虚函数与虚继承

virtual 虚函数修饰词,override代表重入了基类虚函数

#include <iostream>

class Shape {
public:
    virtual void draw() const {
        std::cout << "Drawing a shape." << std::endl;
    }
};

class Circle : public Shape {
public:
    void draw() const override {
        std::cout << "Drawing a circle." << std::endl;
    }
};

3.复杂的指针定义

<1>
int (*p)[10](int*) 

p是一个指针,指向了一个函数,该函数的形参为int*,返回值为int[10]的数组指针

<2>
char (*pfunc[3][4])(void)

这是一个二维数组,其元素为函数指针,函数原型为char func(void)

/*
    定义函数原型指针的指针,并使其指向第一个元素。
*/
char(**func)(void);

/*
    这里其实获取的是行指针 func[4]的指针,指向第一行,但是和第一个元素的
指针位置是重合的 
*/

/*
pfunc是二维数组名,代表的是行指针,&pfunc是代表的[3][4]的数组指针,&pfunc[0][0]是首个元素指针
*/
func = &pfunc[0];

/*
func是函数指针的指针类型(双重指针),+1就是移动一个步长,也就是跳转到下一个函数的指针位置,
所以实际上*解引用之后访问的就是[0][1]这个位置。

并且由于*的优先级低于(),所以要在*外部加上(),使其变成访问函数。
*/

char ch = (*(func + 1))();

<3>

四、keil与编译器特性 

1.keil报错

<1> 无对应的unicode映射字符

        keil工程编译无错误,但是包含时有X,对应的描述为 无可用的unicode字符映射,可能是因为中文路径,更换路径后恢复正常。

2.函数指针和变量的外链性

        函数本身就具有外链接性,不使用extern也可以实现外部链接,但是变量不行,外部文件使用必须使用extern声明,并且要和原型一致,不可将数组外链声明为指针,反之亦然。

3.结构体初始化

        结构体变量可以被声明为静态,然后在外部通过指针访问。结构体的数组成员也可以被声明为静态,然后给定一个U8指针,被外部访问。

/*
结构体初始化不能在函数外直接对成员用等号赋值
struct.member= ?;要用 struct s ={.member = ?};
*/

譬如
static int arr[10];

typedef struct
{
    int* arrdata;
}s;

s s1={.arrdata = arr};

int main(void)
{
    /*或者这样*/
    s1.arrdata=arr;
}

4.函数指针的特殊性

        函数指针是一类特殊的存在,结构体的成员函数(通过函数指针注册的)也可以被声明为静态,因为通过结构体然后访问其成员并不是直接访问而是指针访问,所以被声明为static的函数也可以被外部文件调用。

5.switch_case标签特性

        switch case中,case:具有标签属性(类似goto的标签),最好对每个标签的内容加上{}使其变成局部内容,不然在标签后声明变量会报错。
        因为case块 不是函数,只是一个流程节点,不能定义内容,必须在{ }内或者函数体外才可以定义。

6.typeof与va_arg多参数函数

        typeof的参数不能多重括号,会报错,所以创建可变参数的函数时要注意。
因为可变参数的相关宏,va_arg就利用了typeof;

7.#define的作用域


        #define 具有类似静态全局的特性,他的作用域是定义和包含他的文件。
在函数中定义宏和在函数外定义。其可见性是一样的,都是该文件中的宏都会被替换。

8.__attribute__ 属性

        要实现函数的自动初始化(在main之前运行),可以通过在函数末尾加修饰符实现
如下

<1>
void func(void)__attribute__(constructor(5));
__attribute__代表强制属性(不同编译器的该命令可能不同,但大体相似)
constructor(5),constructor使其在main前运行,
(5)优先级,可以定义多个该属性的函数,数字越小,优先级越高,越早调用

<2>
__attribute__((weak))void func(void) { }
弱定义函数,弱无其他同名函数则使用这个,有则使用另外定义的。
weak 弱定义修饰符,

9.printf

printf有缓存机制,在以下三种情况会刷新缓存,
<1>缓存慢了
<2>\n换行符
<3>主动刷新 fflush(stdout),printf也是IO操作

 stderr是绕开缓存,直接显示的。

printf("%*.*f",width,accuracy);
%*.*f中的*代表其控制参数由后续提供,其中第一个*是宽度,即包括小数点在内的总长度,
第二个是小数点后位数,假如宽度不够会按照实际显示,假如宽度超出了,会在前面添加空格。

10.C语言保留宏

### C语言标准预定义宏

1. **`__FILE__`**:当前源代码文件名。
2. **`__LINE__`**:当前源代码文件中的行号。
3. **`__DATE__`**:源代码文件被编译的日期。
4. **`__TIME__`**:源代码文件被编译的时间。
5. **`__func__`**(C99起):当前函数的名称。
6. **`__STDC__`**:如果编译器遵循ANSI C标准,则定义为1。
7. **`__cplusplus`**:如果编译器支持C++,则定义为一个表示C++版本的值。

11.预编译与兼容设置 

使用define 和 #if的方式来实现kal内核层兼容的方式

#define    TYPE1 (1)
#define    TYPE2 (2)
#define    TYPE3 (3)

// 定义宏来选择操作系统类型
#define OS_TYPE (TYPE3)

#if (OS_TYPE == TYPE1)
    #define PRINT_OS "TYPE1"
#elif (OS_TYPE == TYPE2)
    #define PRINT_OS "TYPE2"
#elif (OS_TYPE == TYPE3)
    #define PRINT_OS "TYPE3"
#else
    #error "Unknown OS type"
#endif

	int main() {
		printf("Current OS: %s\n", PRINT_OS);
		return 0;
	}

    - 使用 `#if`、`#elif` 和 `#else` 来检查 `OS_TYPE` 的值。
    - 在每个条件分支中定义一个 `PRINT_OS` 宏,用于打印当前操作系统类型。
    - 如果没有匹配的操作系统类型,使用 `#error` 指令来生成编译错误。

12.\与/

/和\都是路径分隔符,在linux中/,在windows中\。
..表示上级路径 .表示当前路径,
譬如../test.txt表示上级路径下的test,
./test.txt是当前路径的test。

        

五、操作系统与框架

1.二值信号量

        二值信号量遵循成对原则,take之后必须被再次give才可以使用,即使是同一个任务调用了他。因为信号量每次take都被改变了状态,无关乎是谁调用的。

2.QP框架与事件驱动

        lessloop无尽循环,事件驱动使用了一种活动对象+事件的方式调度,活动对象包括了一些必要信息和一个活动对象处理函数,做为父类,而后使用结构体继承,子类将自己特有的行为注入,写出对应的子类处理函数,并注册到父类的处理函数上,并在内部做了处理。

        事件驱动中任务的创建与RTOS有所不同,RTOS是自己实现while(1),而后通过堆栈切换。在QP事件驱动的框架下,事件处理函数被注册到一个lessloop上,调用了活动对象的内部成员,使其变成一种抽象接口,而非具体实现,在lessloop中,事件处理函数被不停地调度。

        类比到RTOS中,就是使用不同堆栈创建了同一个函数的多个任务。

3.活动对象与事件驱动

/*
active
*/
struct active active;

typedef struct
{
    uint8_t thread; //线程标记
    osevent* queue;//一个原生的事件队列 
    void (*pfunc)(active* me,event* e);//事件处理函数
}active;


typedef struct
{
    actice act;//父类,这样做的好处是,可以将子类指针直接转化为父类指针使用,地址相同
    event my_event; //一个自己的特有事件序列,用来被作为状态条件

}keypress;

typedef struct
{
    uint8_t arg1;
}event;

void keypress_event(active* me, event* e)
{

    switch(me->queue)
    {
        case A:
                {
                   switch(e->arg1):
                            {
                                   case AA:
                                            break;
                            }
                }
            break;

    }

}

总的来说事件框架的主体其实就是一个层次状态机的lessloop,查询式的非阻塞异步实现多任务和状态。

4.RTOS任务平衡 

        在RTOS中有一个时间常数,单个任务运行的时间/总CPU运行的时间,如果该比率超过70%就可能会导致有任务丢失,信息不完全。

六.MCU专题

1.hal_delay注意事项

systick做时基,进行delay的while硬延时不可以放在(优先级比systick高的)
中断函数中,因为当while在高优先级中断里,systick的值无法更新,就会导致异常。
一直while在这里.

优先级是数字越小,优先级越高

2.exti外部中断

        exti外部触发中断记得先清除标志位,并且同一个exti线同一时刻只能有一个GPIO_PIN,譬如PA1 和PC1不能同时使用外部中断连接。

3.芯片型号解读


        在STM32/GD32中 末尾标号譬如GD32F303VCT6,V是管脚数量标识,这里V是100PIN,C是flash大小标识,这里C是512K,T6就是一个温度标识。

2024/8/1

        8月上半开篇,更新了一些语法细节。简单记录了层次状态机与与事件驱动框架。

2024/8/3

        一些小的知识点。

2024/8/9

        一些小的知识点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值