CAN
CAN 控制器根据两根线上的电位差来判断总线电平。总线电平分为显性电平和隐性电平,
二者必居其一。发送方通过使总线电平发生变化,将消息发送给接收方。
1) 多主控制。在总线空闲时,所有单元都可以发送消息(多主控制),而两个以上的单元
同时开始发送消息时,根据标识符(Identifier 以下称为 ID)决定优先级。ID 并不是
表示发送的目的地址,而是表示访问总线的消息的优先级。两个以上的单元同时开始
发送消息时,优先级最高的单元才能继续发送消息。
CAN线物理架构:

CAN信号是一个差分信号,显性电平对应逻辑 0,CAN_H 和 CAN_L 之差为 2.5V 左右。而隐性电平对应逻辑 1,CAN_H 和 CAN_L 之差为 0V。只要有一个单元输出显性电平,总线上即为显性电平,而只有所有的单元都输出隐性电平,总线上才为隐性电平。
由CAN的这一特性,我们可以分析它的仲裁原理:
假设单元 1 和单元 2 同时开始向总线发送数据,开始部分他们的数据格式是一样的, 故无法区分优先级,直到 T 时刻,单元 1 输出隐性电平,而单元 2 输出显性电平,此时单元 1 仲裁失利,立刻转入接收状态工作,不再与单元 2 竞争,而单元 2 则顺利获得总线使用权,继续发送自己的数据。这就实现了仲裁,让连续发送显性电平多的单元获得总线使用权。
在 CAN 总线的起止端都有一个 120Ω的终端电阻,来做阻抗匹配,以减少回波反射。

CAN 协议是通过以下 5 种类型的帧进行的:数据帧,遥控帧,错误帧,过载帧,间隔帧
我们只介绍数据帧,数据帧用于发送单元向接收单元传送数据
数据帧一般由 7 个段构成,即:
1) 帧起始。表示数据帧开始的段,由 1 个位的显性电平表示。
2) 仲裁段。表示该帧优先级的段,标准格式的 ID 有 11 个位,扩展格式的 ID 有 29 个位。标准帧与扩展帧由标志位IDE决定(0使用标准标识符;1使用扩展标识符),是否是数据帧由RTR标志位决定(0为数据帧)。
3) 控制段。表示数据的字节数的段,DLC标志位的值表示了数据长度(0~8)
4) 数据段。数据的内容,一帧可发送 0~8 个字节的数据。
5) CRC 段。检查帧的传输错误的段,由 15 个位的 CRC 顺序和 1 个位的 CRC 界定符(用于分隔的位)组成。
6) ACK 段。表示确认正常接收的段。
7) 帧结束。表示数据帧结束的段,由 7 个位的隐性位组成。

由发送单元发送的每秒钟的位数称为位速率。
STM32F4 的 CAN 一个位只有 3 段:同步段(SYNC_SEG)、时间段 1(BS1)
和时间段 2(BS2)。
CAN波特率公式为:CAN_BaudRate=42M/(CAN_SJW+CAN_BS1+CAN_BS2)/CAN_Prescaler其中CAN_SJW,CAN_BS1,CAN_BS2为设置的位时间,CAN_Prescaler为分频系数。因此,我们只需要知道 BS1 和 BS2 的设置,以及 APB1 的时钟频率(一般为 42Mhz),就可以方便的计算出波特率。

STM32F4 的 CAN 的主要特点有:
 具有 3 个发送邮箱
 具有 3 级深度的 2 个接收 FIFO
 可变的过滤器组(28 个,CAN1 和 CAN2 共享)。每个滤波器组 x 由 2 个 32 位寄存器, CAN_FxR1 和 CAN_FxR2 组成。 通过 CAN_FMR 寄存器的设置,可以设置滤波器的分配方式。

CAN 发送流程为:程序选择 1 个空置的邮箱(TME=1)设置标识符(ID),数据长度和 发送数据设置 CAN_TIxR 的 TXRQ 位为 1,请求发送邮箱挂号(等待成为最高优先级)预定发送(等待总线空闲)发送邮箱空置。
CAN 接收流程为:FIFO 空收到有效报文挂号_1(存入 FIFO 的一个邮箱,这个由硬件 控制,我们不需要理会)收到有效报文挂号_2收到有效报文挂号_3收到有效报文溢出

STM32F4 提供了一种叫环回模式的测试模式,在环回模式下,bxCAN 把发送的报文当作接收的报文并保存(如果可以通过接收过滤)在接收邮箱里。

CAN 的初始化配置步骤:
1)配置相关引脚的复用功能(AF9),使能 CAN 时钟。
CAN1:
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);

GPIO_PinAFConfig(GPIOA, GPIO_PinSource11, GPIO_AF_CAN1);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource12, GPIO_AF_CAN1);

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11 | GPIO_Pin_12;
GPIO_Init(GPIOA, &GPIO_InitStructure);
CAN2:
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN2, ENABLE);

GPIO_PinAFConfig(GPIOB, GPIO_PinSource5, GPIO_AF_CAN2);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_CAN2);

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6;
GPIO_Init(GPIOB, &GPIO_InitStructure);

任务
所谓的任务,其实就是一个死循环函数,该函数实现一定的功能,一个工程可以有很多这
样的任务(最多 255 个),UCOSII 对这些任务进行调度管理,让这些任务可以并发工作,这就是 UCOSII 最基本的功能。Ucos 任务的一般格式为:
void MyTask (void *pdata)
{
任务准备工作… (只执行一次)
While(1)//死循环
{
任务 MyTask 实体代码;
OSTimeDly(t);//调用任务延时函数,释放 cpu 控制权,
}
}
假如我们新建了2个任务为A和B,两个任务死循环中延时时间为1s。如果某个时刻,任务A在执行中,当它执行到延时函数OSTimeDly的时候,它释放cpu控制权,这个时候,任务B获得cpu控制权开始执行,任务B执行过程中,也会调用延时函数延时1s释放 CPU 控制权,这个过程中任务A延时1s到达,重新获得CPU控制权,重新开始执行死循环中的任务实体代码。如此循环,现象就是两个任务交替运行,就好像CPU在同时做两件事情一样。
任务优先级:ucos 中,每个任务都有唯一的一个优先级。优先级是任务的唯一标识。在 UCOSII 中,使用 CPU 的时候,优先级高(数值小)的任务比优先级低的任务具有优先使用权,即任务就绪表中总是优先级最高的任务获得 CPU 使用权,只有高优先级的任务让出 CPU 使用权(比如延时)时,低优先级的任务才能获得 CPU 使用权。

1) 建立任务函数
如果想让 UCOSII 管理用户的任务,必须先建立任务。UCOSII 提供了我们 2 个建立任
务的函数:OSTaskCreate 和 OSTaskCreateExt,我们一般用 OSTaskCreat 函数来创建任务,
该函数原型为:
OSTaskCreate(void(task)(voidpd),voidpdata,OS_STKptos,INTU prio)。
该函数包括 4 个参数:task:是指向任务代码的指针;pdata:是任务开始执行时,传
递给任务的参数的指针;ptos:是分配给任务的堆栈的栈顶指针;prio 是分配给任务的优
先级。
每个任务都有自己的堆栈,堆栈必须申明为 OS_STK 类型,并且由连续的内存空间组
成。可以静态分配堆栈空间,也可以动态分配堆栈空间。
OSTaskCreateExt 也可以用来创建任务,是 OSTaskCreate 的扩展版本,提供一些附件
功能。详细介绍请参考《嵌入式实时操作系统 UCOSII 原理及应用》3.5.2 节。
2) 任务删除函数
所谓的任务删除,其实就是把任务置于睡眠状态,并不是把任务代码给删除了。UCOSII
提供的任务删除函数原型为:
INT8U OSTaskDel(INT8U prio);
其中参数 prio 就是我们要删除的任务的优先级,可见该函数是通过任务优先级来实现
任务删除的。
特别注意:任务不能随便删除,必须在确保被删除任务的资源被释放的前提下才能删
除!
3) 请求任务删除函数
前面提到,必须确保被删除任务的资源被释放的前提下才能将其删除,所以我们通过
向被删除任务发送删除请求,来实现任务释放自身占用资源后再删除。UCOSII 提供的请
求删除任务函数原型为:
INT8U OSTaskDelReq(INT8U prio);
同样还是通过优先级来确定被请求删除任务。
4) 改变任务的优先级函数
UCOSII 在建立任务时,会分配给任务一个优先级,但是这个优先级并不是一成不变的,
而是可以通过调用 UCOSII 提供的函数修改。UCOSII 提供的任务优先级修改函数原型为:
INT8U OSTaskChangePrio(INT8U oldprio,INT8U newprio);
5) 任务挂起函数
任务挂起和任务删除有点类似,但是又有区别,任务挂起只是将被挂起任务的就绪标
志删除,并做任务挂起记录,并没有将任务控制块任务控制块链表里面删除,也不需要释
放其资源,而任务删除则必须先释放被删除任务的资源,并将被删除任务的任务控制块也
给删了。被挂起的任务,在恢复(解挂)后可以继续运行。UCOSII 提供的任务挂起函数
原型为:
INT8U OSTaskSuspend(INT8U prio);
6) 任务恢复函数
有任务挂起函数,就有任务恢复函数,通过该函数将被挂起的任务恢复,让调度器能
够重新调度该函数。UCOSII 提供的任务恢复函数原型为:
INT8U OSTaskResume(INT8U prio);
7) 任务信息查询
在应用程序中我们经常要了解任务信息,查询任务信息函数原型为:
INT8U OSTaskQuery(INT8U prio,OS_TCB *pdata);
这个函数获得的是对应任务的 OS_TCB 中内容的拷贝。
从上面这些函数我们可以看出,对于每个任务,有一个非常关键的参数就是任务优先级 prio,在
UCOS 中,任务优先级可以用来作为任务的唯一标识,所以任务优先级对任务而言是唯一的,
而且是不可重复的。

typedef struct
{
INT8U OSEventType; //事件的类型
INT16U OSEventCnt; //信号量计数器(重要)
void *OSEventPtr; //消息或消息队列的指针
INT8U OSEventGrp; //等待事件的任务组
INT8U OSEventTbl[OS_EVENT_TBL_SIZE];//任务等待表

if OS_EVENT_NAME_EN > 0u

INT8U *OSEventName; //事件名

endif

} OS_EVENT;

信号量
信号量是一类事件。使用信号量的最初目的,是为了给共享资源设立一个标志,该标志表
示该共享资源的占用情况。这样,当一个任务在访问共享资源之前,就可以先对这个标志进行查询,从而在了解资源被占用的情况之后,再来决定自己的行为。
信号量可以分为两种:一种是二值型信号量,另外一种是 N 值信号量。
二值型信号量好比家里的座机,任何时候,只能有一个人占用。而 N 值信号量,则好比公
共电话亭,可以同时有多个人(N 个)使用。
可以把信号量看作是一个公共资源可以同时占用的任务总数,每当有一个任务请求使用一个公共资源,就调用OSSemPend把该信号量减一,而使用完成后就调用OSSemPost 把该信号量加一(当然,如果还有等待的任务则不改变信号量而直接将资源转向该任务即可)

1) 创建信号量函数
在使用信号量之前,我们必须用函数 OSSemCreate 来创建一个信号量:
OS_EVENT OSSemCreate (INT16U cnt);
该函数返回值为已创建信号量的OS_EVENT类指针,而参数 cnt 则是信号量计数器的初始值。
2) 请求信号量函数
任务通过调用函数 OSSemPend 请求信号量,该函数原型如下:
void OSSemPend ( OS_EVENT
pevent, INT16U timeout, INT8U *err);
其中,参数 pevent 是被请求信号量的指针,timeout 为等待时限,err 为错误信息。
为防止任务因得不到信号量而处于长期的等待状态,函数 OSSemPend 允许用参数
timeout 设置一个等待时间的限制,当任务等待的时间超过 timeout 时可以结束等待状态而
进入就绪状态。如果参数 timeout 被设置为 0,则表明任务的等待时间为无限长。
当任务需要访问一个共享资源时,先要请求管理该资源的信号量,这样就可以根据信号量当前是否有效(即信号量的计数器 OSEventCnt 的值是否大于0)来决定该任务是否可以继续运行。
如果信号量有效(即信号量的计数器 OSEventCnt 的值大于0),则把信号量计数器减1,然后继续运行任务。
如果信号量无效(即信号量的计数器 OSEventCnt 的值等于0),则会在等待任务表中把该任务对应的位置1而让任务处于等待状态,并把等待时限 timeout 保存在任务控制块 TCB 的成员 OSTCBDly 中。

3) 发送信号量函数
任务获得信号量,并在访问共享资源结束以后,必须要释放信号量,释放信号量也叫
做发送信号量,发送信号通过 OSSemPost 函数实现 。OSSemPost 函数在对信号量的计数
器操作之前,首先要检查是否还有等待该信号量的任务。如果没有,就把信号量计数器
OSEventCnt 加一;如果有,则调用调度器去运行等待任务中优先级别最高的任务。函数 OSSemPost 的原型为:
INT8U OSSemPost(OS_EVENT pevent);
其中,pevent 为信号量指针。
4) 删除信号量函数
应用程序如果不需要某个信号量了,那么可以调用函数 OSSemDel 来删除该信号量:OS_EVENT
OSSemDel (OS_EVENT pevent,INT8U opt, INT8U err);
其中,pevent 为要删除的信号量指针,opt 为删除条件选项,err 为错误信息。

论cnt的0与1:当 pend请求发出的时候信号量的值减1,当post的时候信号量的值加1,信号量的值0跟1分别是用来同步跟互斥的,什么是同步,什么是互斥呢。。。假设你把信号量的值设为0,有A,B连个任务,当A发出pend请求的时候它发现此事的信号量值为0就把它减1然后挂起等待,等待到什么时候呢?等待任务B执行post操作把信号量的值加1然后唤醒进程A,然后两个进程同步并发执行。通俗点解释就是任务A执行到某个地方的时候先停下来睡觉,等待任务B来叫醒它,然后两个任务一起并发运行,也就是几乎同时从那个点开始运行哈。
假设信号量的值是1,就是用来互斥的,A,B两个任务只能其中一个任务pend执行成功返回,此时信号量的值是0,另一个在pend请求的时候就必须把自己挂起等待刚刚那个请求成功的任务执行post操作才能唤醒继续执行。