RT-thread设备驱动组件之PIN设备
?在RT-thread2.0.0正式版中引入了pin设备作为杂类设备,其设备驱动文件
pin.c在rt-thread-2.0.1\components\drivers\misc中,主要用于操作芯片
GPIO,如点亮led,按键等。同时对于相应的芯片平台,需要自行编写底层gpio
驱动,如gpio.c。本文主要涉及的pin设备文件有:驱动框架文件(pin.c,pin.h),
底层硬件驱动文件(gpio.c,gpio.h)。在应用用PIN设备时,需要在rtconfig.h
中宏定义#defineRT_USING_PIN。
一、PIN设备驱动框架
在pin.c中定义了一个静态的pin设备对象staticstructrt_device_pin_hw_pin,其中
结构体类型structrt_device_pin在pin.h中定义为:
/pindeviceandoperationsforRT-Thread/
structrt_device_pin
{
structrt_deviceparent;
conststructrt_pin_opsops;
};
structrt_device_pin_mode
{
rt_uint16_tpin;//pinindexinpins[]ofgpio.c
rt_uint16_tmode;
};
structrt_device_pin_status
{
rt_uint16_tpin;//pinindexinpins[]ofgpio.c
rt_uint16_tstatus;
};
structrt_pin_ops
{
void(pin_mode)(structrt_devicedevice,rt_base_tpin,rt_base_t
mode);
void(pin_write)(structrt_devicedevice,rt_base_tpin,rt_base_t
value);
int(pin_read)(structrt_devicedevice,rt_base_tpin);
/TODO:addGPIOinterrupt/
};
在pin.c中主要实现了_pin_read,_pin_write,_pin_control三个函数,同时将这三个
函数注册为_hw_pin设备的统一接口函数:
intrt_device_pin_register(constcharname,conststructrt_pin_ops
ops,voiduser_data)
{
_hw_pin.parent.type=RT_Device_Class_Miscellaneous;
_hw_pin.parent.rx_indicate=RT_NULL;
_hw_pin.parent.tx_complete=RT_NULL;
_hw_pin.parent.init=RT_NULL;
_hw_pin.parent.open=RT_NULL;
_hw_pin.parent.close=RT_NULL;
_hw_pin.parent.read=_pin_read;
_hw_pin.parent.write=_pin_write;
_hw_pin.parent.control=_pin_control;
_hw_pin.ops=ops;
_hw_pin.parent.user_data=user_data;
/registeracharacterdevice/
rt_device_register(&_hw_pin.parent,name,RT_DEVICE_FLAG_RDWR);
return0;
}
最后,在pin.c文件中将rt_pin_mode,rt_pin_write,rt_pin_read三个函数加入到finsh
的函数列表中,用于调试。
二、底层硬件驱动
在gpio.c中主要实现structrt_pin_ops中的三个接口函数:stm32_pin_mode,
stm32_pin_write,stm32_pin_read:
conststaticstructrt_pin_ops_stm32_pin_ops=
{
stm32_pin_mode,
stm32_pin_write,
stm32_pin_read,
};
同时注册_hw_pin设备,其设备名称为“pin”,PIN设备硬件初始化:
intstm32_hw_pin_init(void)
{
rt_device_pin_register("pin",&_stm32_pin_ops,RT_NULL);
return0;
}
INIT_BOARD_EXPORT(stm32_hw_pin_init);//stm32_hw_pin_initwillbe
calledinrt_components_board_init()
三、PIN设备初始化
在gpio.c中修改使用的IO口数组:
/STM32GPIOdriver/
structpin_index
{
intindex;
uint32_trcc;
GPIO_TypeDefgpio;
uint32_tpin;
};
/LED->PD12,PD13,PD14,PD15;USERButton->PA0/
staticconststructpin_indexpins[]=
{
{0,RCC_AHB1Periph_GPIOD,GPIOD,GPIO_Pin_12},//green
{1,RCC_AHB1Periph_GPIOD,GPIOD,GPIO_Pin_13},//orange
{2,RCC_AHB1Periph_GPIOD,GPIOD,GPIO_Pin_14},//red
{3,RCC_AHB1Periph_GPIOD,GPIOD,GPIO_Pin_15},//blue
{4,RCC_AHB1Periph_GPIOA,GPIOA,GPIO_Pin_0},//userbutton
};
此外在gpio_pin.c的外设初始化函数中,需要先调用rt_device_open函数(尽管
该函数没有在底层实现),保证pin设备对象类的设备引用计数值ref_count不为0,这
样才可正常使用rt_device_control,rt_device_write,rt_device_read函数操作GPIO
口:
staticstructrt_device_pin_modeled_mode[]=
{
{0,PIN_MODE_OUTPUT},
{1,PIN_MODE_OUTPUT},
{2,PIN_MODE_OUTPUT},
{3,PIN_MODE_OUTPUT},
};
staticstructrt_device_pin_statusled_status[]=
{
{0,PIN_HIGH},/0:greenon/
{1,PIN_HIGH},/1:orangeon/
{2,PIN_HIGH},/2:redon/
{3,PIN_HIGH},/3:blueon/
{0,PIN_LOW},/4:greenoff/
{1,PIN_LOW},/5:orangeoff/
{2,PIN_LOW},/6:redoff/
{3,PIN_LOW},/7:blueoff/
};
/itcan''tbePIN_MODE_INPUT_PULLUP,orthekeyalwayswillkeepPIN_HIGH
status/
staticstructrt_device_pin_modekey_mode={4,PIN_MODE_INPUT};
staticstructrt_device_pin_statuskey_status={4,PIN_LOW};
staticstructrt_device_pinpin_device;
staticrt_err_tgpio_pin_init(constcharpin_device_name)
{
pin_device=(structrt_device_pin
)rt_device_find(pin_device_name);
if(pin_device==RT_NULL)
{
rt_kprintf("pindeviceforgpio%snotfound!\r\n",
pin_device_name);
return-RT_ENOSYS;
}
/oflaghasnomeaningforpindevice,sosettoRT_NULL/
if(rt_device_open(&pin_device->parent,RT_NULL)==RT_EOK)
{
/initled/
for(inti=0;i<(sizeof(led_mode)/sizeof(led_mode[0]));i++)
{
rt_device_control(&pin_device->parent,RT_NULL,
&led_mode[i]);
rt_device_write(&pin_device->parent,RT_NULL,
&led_status[i],sizeof(led_status[i]));//initallledPIN_HIGH
}
/initkey/
rt_device_control(&pin_device->parent,RT_NULL,&key_mode);
}
return0;
}
intrt_gpio_pin_init(void)
{
gpio_pin_init("pin");
return0;
}
INIT_APP_EXPORT(rt_gpio_pin_init);
RTthread设备驱动组件之USART设备
本文以stm32f4xx平台介绍串口驱动,主要目的是:1、RTT中如何编写中断处理
程序;2、如何编写RTT设备驱动接口代码;3、了解串行设备的常见处理机制。所涉及的
主要源码文件有:驱动框架文件(usart.c,usart.h),底层硬件驱动文件(serial.c,
serial.h)。应用串口设备驱动时,需要在rtconfig.h中宏定义#define
RT_USING_SERIAL。
一、RTT的设备驱动程序概述
串口
串口设备的工作模式:
?INT_RX-中断接收,读取到缓冲区中,然后提示到上层应用来把数据读
取走;
?INT_TX-中断发送,发送到硬件FIFO中,发送完成后给出中断;
?DMA_RX-接收时由上层应用给出缓冲区,当收到数据后调用rx_indicate
提示给上层应用;
?DMA_TX-数据链接在data_queue中,触发DMA把数据发送出去,完成后
调用tx_complete返回给上层应用;
?当字符设备接收到数据时,可以先行放在一个环形缓冲中,等待上层应用
来读取;
?发送数据时,可以根据需要是否采用DMA方式发送。
?
编写uart的驱动程序,首先需要了解RTT的设备框架,这里以usart的驱动来具体
分析RTT的IO设备管理。注:参考《RTT实时操作系统编程指南》I/O设备管理一章。
我们可以将USART的硬件驱动分成两个部分,如下图所示
+----------------------+
|rtt下的usart设备驱动|
|----------------------|
|usart硬件初始化代码|
|----------------------|
|usart硬件|
+----------------------+
实际上,在缺乏操作系统的平台,即裸机平台上,我们通常只需要编写USART硬件
初始化代码即可。而引入了RTOS,如RTT后,RTT中自带IO设备管理层,它是为了将
各种各样的硬件设备封装成具有统一的接口的逻辑设备,以方便管理及使用。让我们从下向
上看,先来看看USART硬件初始化程序,这部分代码位于usart.c和usart.h中。
二、USART硬件初始化
假如在接触RTT之前,你已经对stm32很熟悉了,那么此文件中定义的函数名一定让你
倍感亲切。这里实现的函数有:
?staticvoidRCC_Configuration(void);
?staticvoidGPIO_Configuration(void);
?staticvoidNVIC_Configuration(void);
?staticvoidDMA_Configuration(void);
?voidstm32_hw_usart_init();
前四个函数,是跟ST官方固件库提供的示例代码的名字保持一致。这些函数内部也是直接
调用官方库代码实现的。具体不再赘述。对STM32裸机开发不太熟悉的朋友,建议先去官
方网站下载官方固件源码包,以及应用手册和示例程序,ST提供了大量的文档和示例代码,
利用好这些资源可以极大地加快开发。
下面重点看一下usart.c中stm32_hw_usart_init():
intstm32_hw_usart_init(void)
{
structstm32_uartuart;
structserial_configureconfig=RT_SERIAL_CONFIG_DEFAULT;
RCC_Configuration();
GPIO_Configuration();
#ifdefRT_USING_UART2
uart=&uart2;
serial2.ops=&stm32_uart_ops;
serial2.config=config;
NVIC_Configuration(&uart2);
/registerUART2device/
rt_hw_serial_register(&serial2,
"uart2",
RT_DEVICE_FLAG_RDWR|RT_DEVICE_FLAG_INT_RX,
//dev->flag(notdev->open_flag)
uart);
#endif/RT_USING_UART2/
#ifdefRT_USING_UART3
...
#endif/RT_USING_UART3/
return0;
}
//INIT_BOARD_EXPORT(stm32_hw_usart_init);//itmustbeinvokedin
board.c(rt_hw_board_initforsettingCONSOLE_DEVICE)
首先该函数调用RCC_Configuration()和GPIO_Configuration()打开串口外设时
钟和IO口配置;调用NVIC_Configuration(&uart2)设置串口中断,其中参数&uart2为
一个自定义结构体类型:
/STM32uartdriver/
structstm32_uart
{
USART_TypeDefuart_device;
IRQn_Typeirq;
};
接着初始化设备类对象serial2中的ops和config两个参数,并在usart.c中实现
了stm32_uart_ops中的四个函数。
staticconststructrt_uart_opsstm32_uart_ops=
{
stm32_configure,
stm32_control,
stm32_putc,
stm32_getc,
};
structserial_configureconfig=RT_SERIAL_CONFIG_DEFAULT;
最后注册串口设备serial2,注册函数位于serial.c中:
/registerUART2device/
rt_hw_serial_register(&serial2,
"uart2",
RT_DEVICE_FLAG_RDWR|RT_DEVICE_FLAG_INT_RX,
//dev->flag(notdev->open_flag)
uart);
显然,函数stm32_hw_usart_init(),顾名思义,是用于初始化USART硬件的函
数,因此这个函数一定会在USART使用之前被调用。搜索工程发现,这个函数是在board.c
中rt_hw_board_init函数中被调用,而rt_hw_board_init函数又是在startup.c里的
rtthread_startup函数中调用的。进一步在startup.c的main函数中调用的,我们将实
际的路径调用过程绘制如下。
startup.cmain()
--->startup.crtthread_startup()
--->board.crt_hw_board_init()
--->usart.crt_hw_usart_init()
到这里,USART硬件的初始化工作已经完成完成了99%,下一步,我们需要为
USART编写代码,将其纳入到RTT的设备管理层之中,正如前面所说,这部分代码在
serial.c中实现。我们来重点分析这一文件。
三、在RTT下使用USART,将USART纳入RTT的IO设备层中
相对于stm32的内核来说,USART是一种低速的串行设备,并且为了最大的发挥的
MCU的性能,因此使用查询方式发送、中断方式接收(发送也可以使用DMA方式)。这
些已经在usart.c中使能了。首先看一些serial.h中的重要数据结构:
/
SerialFIFOmode
/
structrt_serial_rx_fifo
{
/softwarefifo/
rt_uint8_tbuffer;
rt_uint16_tput_index,get_index;
};
structrt_serial_tx_fifo
{
structrt_completioncompletion;
};
/
SerialDMAmode
/
structrt_serial_rx_dma
{
rt_bool_tactivated;
};
structrt_serial_tx_dma
{
rt_bool_tactivated;
structrt_data_queuedata_queue;
};
structrt_serial_device
{
structrt_deviceparent;
conststructrt_uart_opsops;
structserial_configureconfig;
voidserial_rx;
voidserial_tx;
};
typedefstructrt_serial_devicert_serial_t;
/
uartoperators
/
structrt_uart_ops
{
rt_err_t(configure)(structrt_serial_deviceserial,struct
serial_configurecfg);
rt_err_t(control)(structrt_serial_deviceserial,intcmd,void
arg);
int(putc)(structrt_serial_deviceserial,charc);
int(getc)(structrt_serial_deviceserial);
rt_size_t(dma_transmit)(structrt_serial_deviceserial,const
rt_uint8_tbuf,rt_size_tsize,intdirection);
};
在serial.c中主要实现rtthread系统的IO设备统一接口函数,并注册串口设备:
/
serialregister
/
rt_err_trt_hw_serial_register(structrt_serial_deviceserial,
constcharname,
rt_uint32_tflag,
voiddata)
{
structrt_devicedevice;
RT_ASSERT(serial!=RT_NULL);
device=&(serial->parent);
device->type=RT_Device_Class_Char;
device->rx_indicate=RT_NULL;
device->tx_complete=RT_NULL;
device->init=rt_serial_init;
device->open=rt_serial_open;
device->close=rt_serial_close;
device->read=rt_serial_read;
device->write=rt_serial_write;
device->control=rt_serial_control;
device->user_data=data;
/registeracharacterdevice/
returnrt_device_register(device,name,flag);
}
对于串口发送数据,默认采用查询方式。因为串口设备注册的时候,其设备标志为
RT_DEVICE_FLAG_RDWR|RT_DEVICE_FLAG_INT_RX,没有
RT_DEVICE_FLAG_DMA_TX或RT_DEVICE_FLAG_INT_TX标志。数据发送流程为:
rt_device_write()-->rt_serial_write()-->_serial_poll_tx()-->stm32_putc()。
考虑串口接受数据的情况,串口收到一个字节的数据,就会触发串口中断
USARTx_IRQHandler,数据字节会存放于串口的硬件寄存器中。但是在RTOS中,通常
存在多个线程,如果某个处理串口数据的线程在没有串口数据时阻塞,当下一串口数据到来
时,如果该数据线程依然没有唤醒并启动,并读取串口字节,则上一个串口字节丢失了,因
此这不是一个优良的设计,我们需要设计一种机制来解决这种潜在的问题。实际上,缓冲机
制可以大大缓解这个问题。数据读取流程为:
rt_device_read()-->rt_serial_read()-->rt_hw_serial_isr()-->stm32_getc()。
所谓缓冲机制,简略的来说,即开辟一个缓冲区,可以是静态数组,也可以是malloc
(或mempool)申请的动态缓冲区。在串口中断中,先从串口的硬件寄存器中读取数据,
并保存到缓冲区中。这种情况下,我们需要两个变量,一个用于标记当前写入的位置,另外
一个用来表示已经被处理的数据的位置。这样当数据处理线程阻塞时,连续收到的数据会保
存到缓冲区中而避免了丢失。当中断中已经接收到了一些串口数据后,数据处理线程终于就
绪,并开始处理数据,通常来说处理数据的速度必然比接受数据要快,因此这样就能解决前
面所说的问题。聪明的读者发现了,还有一个小问题,缓冲区的长度必然是有限的,终归会
有用到头的时候,那该怎么办呢?别担心,缓冲区前面已经被处理过的数据所占用的空间按
自然可以重复使用,即,当接收指针指向了缓冲区末尾时,只要缓冲区头的数据已经被处理
过了,自然可以直接将缓冲区指针从新设置为头,对于表示已处理的指针变量同理。这样这
个缓冲区也就成为了一个环形缓冲区。
下面重点看一下中断函数:
#ifdefined(RT_USING_UART2)
/UART2devicedriverstructure/
structstm32_uartuart2=
{
USART2,
USART2_IRQn,
};
structrt_serial_deviceserial2;
voidUSART2_IRQHandler(void)
{
structstm32_uartuart;
uart=&uart2;
/enterinterrupt/
rt_interrupt_enter();
if(USART_GetITStatus(uart->uart_device,USART_IT_RXNE)!=RESET)
{
rt_hw_serial_isr(&serial2,
RT_SERIAL_EVENT_RX_IND);//USART_IT_RXNEisclearedautomaticallyby
readingUSART_DRinstm32_getc()
}
if(USART_GetITStatus(uart->uart_device,USART_IT_TC)!=RESET)
{
/clearinterrupt/
USART_ClearITPendingBit(uart->uart_device,USART_IT_TC);
}
/leaveinterrupt/
rt_interrupt_leave();
}
#endif/RT_USING_UART2/
该中断函数在usart.c中,在RTT下的每一个中断服务子程序的入口都调用了
rt_interrupt_enter(),在中断函数的子程序的出口则调用了
rt_interrupt_leave()。
/ISRforserialinterrupt/
voidrt_hw_serial_isr(structrt_serial_deviceserial,intevent)
{
switch(event&0xff)
{
caseRT_SERIAL_EVENT_RX_IND:
{
intch=-1;
rt_base_tlevel;
structrt_serial_rx_fiforx_fifo;
rx_fifo=(structrt_serial_rx_fifo)serial->serial_rx;
RT_ASSERT(rx_fifo!=RT_NULL);
/interruptmodereceive/
RT_ASSERT(serial->parent.open_flag&
RT_DEVICE_FLAG_INT_RX);
while(1)
{
ch=serial->ops->getc(serial);
if(ch==-1)break;
/disableinterrupt/
level=rt_hw_interrupt_disable();
rx_fifo->buffer[rx_fifo->put_index]=ch;
rx_fifo->put_index+=1;
if(rx_fifo->put_index>=serial->config.bufsz)
rx_fifo->put_index=0;
/ifthenextpositionisreadindex,discardthis''read
char''/
if(rx_fifo->put_index==rx_fifo->get_index)
{
rx_fifo->get_index+=1;
if(rx_fifo->get_index>=serial->config.bufsz)
rx_fifo->get_index=0;
}
/enableinterrupt/
rt_hw_interrupt_enable(level);
}
/invokecallback/
if(serial->parent.rx_indicate!=RT_NULL)
{
rt_size_trx_length;
/getrxlength/
level=rt_hw_interrupt_disable();
rx_length=(rx_fifo->put_index>=rx_fifo->get_index)?
(rx_fifo->put_index-rx_fifo->get_index):
(serial->config.bufsz-(rx_fifo->get_index-
rx_fifo->put_index));
rt_hw_interrupt_enable(level);
serial->parent.rx_indicate(&serial->parent,
rx_length);
}
break;
}
caseRT_SERIAL_EVENT_TX_DONE:
{
structrt_serial_tx_fifotx_fifo;
tx_fifo=(structrt_serial_tx_fifo)serial->serial_tx;
rt_completion_done(&(tx_fifo->completion));
break;
}
caseRT_SERIAL_EVENT_TX_DMADONE:
{
constvoiddata_ptr;
rt_size_tdata_size;
constvoidlast_data_ptr;
structrt_serial_tx_dmatx_dma;
tx_dma=(structrt_serial_tx_dma)serial->serial_tx;
rt_data_queue_pop(&(tx_dma->data_queue),&last_data_ptr,
&data_size,0);
if(rt_data_queue_peak(&(tx_dma->data_queue),&data_ptr,
&data_size)==RT_EOK)
{
/transmitnextdatanode/
tx_dma->activated=RT_TRUE;
serial->ops->dma_transmit(serial,data_ptr,data_size,
RT_SERIAL_DMA_TX);
}
else
{
tx_dma->activated=RT_FALSE;
}
/invokecallback/
if(serial->parent.tx_complete!=RT_NULL)
{
serial->parent.tx_complete(&serial->parent,
(void)last_data_ptr);
}
break;
}
caseRT_SERIAL_EVENT_RX_DMADONE:
{
intlength;
structrt_serial_rx_dmarx_dma;
rx_dma=(structrt_serial_rx_dma)serial->serial_rx;
/getDMArxlength/
length=(event&(~0xff))>>8;
serial->parent.rx_indicate(&(serial->parent),length);
rx_dma->activated=RT_FALSE;
break;
}
}
}
该函数位于serial.c中,默认情况下usart的rt_device结构体中rx_indicate
域被置空,因此不会运行这一段代码。如果使用
rt_device_set_rx_indicate(rt_device_tdev,rt_err_t(
rx_ind)(rt_device_tdev,rt_size_tsize))函数为一个串口设备注册了接收
事件回调函数,在该串口接收到数据后,就会调用之前注册的rx_ind函数,将
当前设备指针以及待读取的数据长度作为调用参数传递给用户。
RT-thread设备驱动组件之SPI设备
本文主要介绍RT-thread中的SPI设备驱动,涉及到的文件主要有:驱动框架文件
(spi_dev.c,spi_core.c,spi.h),底层硬件驱动文件(spi_hard.c,spi_hard.h)。
这里spi_hard.c和spi_hard.h是指利用MCU的硬件SPI接口,而不是通过GPIO口来
模拟SPI时序。应用SPI设备驱动时,需要在rtconfig.h中宏定义#define
RT_USING_SPI。
SPI
SPI的通信原理很简单,它以主从方式工作,这种模式通常有一个主设备和一个或多个
从设备,需要至少4根线,事实上3根也可以(用于单向传输时,也就是半双工方式)。也
是所有基于SPI的设备共有的,它们是:
oMOSI–SPI总线主机输出/从机输入(SPIBusMasterOutput/SlaveInput)
oMISO–SPI总线主机输入/从机输出(SPIBusMasterInput/SlaveOutput)
oSCLK–时钟信号,由主设备产生
oCS–从设备使能信号,由主设备控制(Chipselect),有的芯片这个引脚叫做
SS。
其中CS是控制芯片是否被选中的,也就是说只有片选信号为预先规定的使能信号时
(高电位或低电位),对此芯片的操作才有效。这就允许在同一总线上连接多个SPI设备成
为可能。
接下来就负责通讯的3根线了。通讯是通过数据交换完成的,这里先要知道SPI是串
行通讯协议,也就是说数据是一位一位的传输的。这就是SCLK时钟线存在的原因,由SCLK
提供时钟脉冲,MOSI,MISO则基于此脉冲完成数据传输。数据输出通过MOSI线,数据在
时钟上升沿或下降沿时改变,在紧接着的下降沿或上升沿被读取。完成一位数据传输,输入
也使用同样原理。这样在至少8次时钟信号的改变(上沿和下沿为一次),就可以完成8
位数据的传输。
要注意的是,SCLK信号线只由主设备控制,从设备不能控制信号线。同样,在一个基
于SPI的设备中,至少有一个主控设备。这样传输的特点:这样的传输方式有一个优点,与
普通的串行通讯不同,普通的串行通讯一次连续传送至少8位数据,而SPI允许数据一位一
位的传送,甚至允许暂停,因为SCLK时钟线由主控设备控制,当没有时钟跳变时,从设备
不采集或传送数据。也就是说,主设备通过对SCLK时钟线的控制可以完成对通讯的控制。
SPI还是一个数据交换协议:因为SPI的数据输入和输出线独立,所以允许同时完成数
据的输入和输出。不同的SPI设备的实现方式不尽相同,主要是数据改变和采集的时间不同,
在时钟信号上沿或下沿采集有不同定义,具体请参考相关器件的文档。
在点对点的通信中,SPI接口不需要进行寻址操作,且为全双工通信,显得简单高效。
在多个从设备的系统中,每个从设备需要独立的使能信号。
1.SPIBUS构架
SPI核心层是SPIBUS。针对每个平台,SPIBUSCore的功能需要底层的SPIBUSDriver来
支撑,即SPIBUSDriver是与底层硬件最接近的一层,它会完全根据芯片手册规范操作SPI
的寄存器,时钟输出,片选信号等。
如果SPIBUS是作为Master模式来使用,那么在每个SPIBUS上有一层虚拟的接口层:
SPIDevice(每个CS会有一个SPIDevice与它相对应)。SPI上具体挂接的设备由相应的驱动
程序进行驱动,它们与SPIBUS相接口的是SPIDevice,所以SPIDevice也可以看成是SPIBUS
的接口层。
如果SPIBUS是作为Slave模式来使用,则此时SPIBUS上只有一个设备(这个设备也同
样称之为SPIdevice,在这个设备上是否形成多通道逻辑设备由上层驱动考虑)。
其中,SPIBUSCore、SPIBUSDrv与SPIMaster模式中的代码完全通用,不需要底层再提
供相应的代码。
一、SPI设备驱动框架
先来看spi.h中的一些数据结构:
SPImessagestructure
/structrt_spi_message
{
constvoidsend_buf;
voidrecv_buf;
rt_size_tlength;
structrt_spi_messagenext;
unsignedcs_take:1;
unsignedcs_release:1;
};
/
SPIconfigurationstructure
/structrt_spi_configuration
{
rt_uint8_tmode;
rt_uint8_tdata_width;
rt_uint16_treserved;
rt_uint32_tmax_hz;
};
structrt_spi_ops;structrt_spi_bus
{
structrt_deviceparent;
conststructrt_spi_opsops;
structrt_mutexlock;
structrt_spi_deviceowner;
};
/
SPIoperators
/structrt_spi_ops
{
rt_err_t(configure)(structrt_spi_devicedevice,struct
rt_spi_configurationconfiguration);
rt_uint32_t(xfer)(structrt_spi_devicedevice,structrt_spi_message
message);
};
/
SPIVirtualBUS,onedevicemustconnectedtoavirtualBUS
/structrt_spi_device
{
structrt_deviceparent;
structrt_spi_busbus;
structrt_spi_configurationconfig;
};#defineSPI_DEVICE(dev)((structrt_spi_device)(dev))
spi_core.c,spi_dev.c这两个文件位于RTT\components\drivers\spi目录下,而spi.h
头文件位于RTT\\components\drivers\include\drivers目录下。可在MKD工程的
Drivers组下将上面两个源文件加进行,并将spi.h头文件所在目录添加到工程的include
path下。
spi_core.c文件实现了spi的抽象操作,如注册spi总线(spi_bus),向SPI总线添加设
备函数等。注:这里将MCU的一路spi外设虚拟成spi总线,然后总线上可以挂很多spi
设备(spi_device),一个spi_device有一个片选cs。spi总线和spi设备要在RTT中可
以生效就必须先向RTT注册,因此就需要使用上面的注册SPI总线函数和向SPI总线中添
加SPI设备。
spi_core.c还包含了配置SPI函数,发送和接收等通信函数,占用和释放SPI总线函数及
选择SPI设备函数。这些函数都是抽象出来的,反映出SPI总线上的一些常规操作。真正
执行这些操作的过程并不在spi_core.c源文件中,实际上,这些操作信息都是通过注册SPI
总线和向总线添加SPI设备时这些操作集就已经"注册"下来了,真正操作时是通过注册信
息内的操作函数去实现,也可以说是一种回调操作。spi_core.c中实现的函数主要有:
rt_spi_bus_register();rt_spi_bus_attach_device();rt_spi_configure();
rt_spi_send_then_send();rt_spi_send_then_recv();rt_spi_transfer();
rt_spi_transfer_message();rt_spi_take_bus();rt_spi_release_bus();
rt_spi_take();rt_spi_release()。
而spi_dev.c实现了SPI设备的一些抽象操作,比如读,写,打开,关闭,初始化等,当
然当MCU操作SPI设备的时候,是需要通过SPI总线与SPI设备进行通信的,既然通信
就必然会有SPI通信协议,但是通信协议并不在这里具体,spi_dev.c这里还只是SPI设
备的抽象操作而已,它只是简单地调用spi_core.c源文件中的抽象通信而已,具体实现还
是要靠上层通过SPI总线或SPI设备注册下来的信息而实现的。spi_device.c中实现的函
数主要
有:_spi_bus_device_read();_spi_bus_device_write();_spi_bus_device_con
trol();rt_spi_bus_device_init();_spidev_device_read();_spidev_device_write
();_spidev_device_control();rt_spidev_device_init()。
在确保了spi_core.c,spi_dev.c和spi.h这三个源文件在MDK工程内之后,接着往下
走。
二、底层硬件驱动
在spi_hard.c中实现configure和xfer函数(默认没有使用DMA):
staticrt_err_tconfigure(structrt_spi_devicedevice,structrt_spi_configurationconfiguration);
staticrt_uint32_txfer(structrt_spi_devicedevice,structrt_spi_messagemessage);
staticstructrt_spi_opsstm32_spi_ops=
{
configure,
xfer
};
rt_inlineuint16_tget_spi_BaudRatePrescaler(rt_uint32_tmax_hz)
{
uint16_tSPI_BaudRatePrescaler;
/STM32F10xSPIMAX18Mhz/
if(max_hz>=SystemCoreClock/2&&SystemCoreClock/2<=18000000)
{
SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_2;
}
elseif(max_hz>=SystemCoreClock/4)
{
SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_4;
}
elseif(max_hz>=SystemCoreClock/8)
{
SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_8;
}
elseif(max_hz>=SystemCoreClock/16)
{
SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_16;
}
elseif(max_hz>=SystemCoreClock/32)
{
SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_32;
}
elseif(max_hz>=SystemCoreClock/64)
{
SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_64;
}
elseif(max_hz>=SystemCoreClock/128)
{
SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_128;
}
else
{
/minprescaler256/
SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_256;
}
returnSPI_BaudRatePrescaler;
}
staticrt_err_tconfigure(structrt_spi_devicedevice,structrt_spi_configurationconfiguration)
{
structstm32_spi_busstm32_spi_bus=(structstm32_spi_bus)device->bus;
SPI_InitTypeDefSPI_InitStructure;
SPI_StructInit(&SPI_InitStructure);
/data_width/
if(configuration->data_width<=8)
{
SPI_InitStructure.SPI_DataSize=SPI_DataSize_8b;
}
elseif(configuration->data_width<=16)
{
SPI_InitStructure.SPI_DataSize=SPI_DataSize_16b;
}
else
{
returnRT_EIO;
}
/baudrate/
SPI_InitStructure.SPI_BaudRatePrescaler=
get_spi_BaudRatePrescaler(configuration->max_hz);
/CPOL/
if(configuration->mode&RT_SPI_CPOL)
{
SPI_InitStructure.SPI_CPOL=SPI_CPOL_High;
}
else
{
SPI_InitStructure.SPI_CPOL=SPI_CPOL_Low;
}
/CPHA/
if(configuration->mode&RT_SPI_CPHA)
{
SPI_InitStructure.SPI_CPHA=SPI_CPHA_2Edge;
}
else
{
SPI_InitStructure.SPI_CPHA=SPI_CPHA_1Edge;
}
/MSBorLSB/
if(configuration->mode&RT_SPI_MSB)
{
SPI_InitStructure.SPI_FirstBit=SPI_FirstBit_MSB;
}
else
{
SPI_InitStructure.SPI_FirstBit=SPI_FirstBit_LSB;
}
SPI_InitStructure.SPI_Direction=SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_Mode=SPI_Mode_Master;
SPI_InitStructure.SPI_NSS=SPI_NSS_Soft;
/initSPI/
SPI_I2S_DeInit(stm32_spi_bus->SPI);
SPI_Init(stm32_spi_bus->SPI,&SPI_InitStructure);
/EnableSPI_MASTER/
SPI_Cmd(stm32_spi_bus->SPI,ENABLE);
SPI_CalculateCRC(stm32_spi_bus->SPI,DISABLE);
returnRT_EOK;
};
staticrt_uint32_txfer(structrt_spi_devicedevice,structrt_spi_messagemessage)
{
structstm32_spi_busstm32_spi_bus=(structstm32_spi_bus)device->bus;
structrt_spi_configurationconfig=&device->config;
SPI_TypeDefSPI=stm32_spi_bus->SPI;
structstm32_spi_csstm32_spi_cs=device->parent.user_data;
rt_uint32_tsize=message->length;
/takeCS/
if(message->cs_take)
{
GPIO_ResetBits(stm32_spi_cs->GPIOx,stm32_spi_cs->GPIO_Pin);
}
#ifdefSPI_USE_DMA
if(message->length>32)
{
if(config->data_width<=8)
{
DMA_Configuration(stm32_spi_bus,message->send_buf,message->recv_buf,
message->length);
SPI_I2S_DMACmd(SPI,SPI_I2S_DMAReq_Tx|SPI_I2S_DMAReq_Rx,ENABLE);
while(DMA_GetFlagStatus(stm32_spi_bus->DMA_Channel_RX_FLAG_TC)==
RESET
||DMA_GetFlagStatus(stm32_spi_bus->DMA_Channel_TX_FLAG_TC)==
RESET);
SPI_I2S_DMACmd(SPI,SPI_I2S_DMAReq_Tx|SPI_I2S_DMAReq_Rx,DISABLE);
}
//rt_memcpy(buffer,_spi_flash_buffer,DMA_BUFFER_SIZE);
//buffer+=DMA_BUFFER_SIZE;
}
else
#endif
{
if(config->data_width<=8)
{
constrt_uint8_tsend_ptr=message->send_buf;
rt_uint8_trecv_ptr=message->recv_buf;
while(size--)
{
rt_uint8_tdata=0xFF;
if(send_ptr!=RT_NULL)
{
data=send_ptr++;
}
//Waituntilthetransmitbufferisempty
while(SPI_I2S_GetFlagStatus(SPI,SPI_I2S_FLAG_TXE)==RESET);
//Sendthebyte
SPI_I2S_SendData(SPI,data);
//Waituntiladataisreceived
while(SPI_I2S_GetFlagStatus(SPI,SPI_I2S_FLAG_RXNE)==RESET);
//Getthereceiveddata
data=SPI_I2S_ReceiveData(SPI);
if(recv_ptr!=RT_NULL)
{
recv_ptr++=data;
}
}
}
elseif(config->data_width<=16)
{
constrt_uint16_tsend_ptr=message->send_buf;
rt_uint16_trecv_ptr=message->recv_buf;
while(size--)
{
rt_uint16_tdata=0xFF;
if(send_ptr!=RT_NULL)
{
data=send_ptr++;
}
//Waituntilthetransmitbufferisempty
while(SPI_I2S_GetFlagStatus(SPI,SPI_I2S_FLAG_TXE)==RESET);
//Sendthebyte
SPI_I2S_SendData(SPI,data);
//Waituntiladataisreceived
while(SPI_I2S_GetFlagStatus(SPI,SPI_I2S_FLAG_RXNE)==RESET);
//Getthereceiveddata
data=SPI_I2S_ReceiveData(SPI);
if(recv_ptr!=RT_NULL)
{
recv_ptr++=data;
}
}
}
}
/releaseCS/
if(message->cs_release)
{
GPIO_SetBits(stm32_spi_cs->GPIOx,stm32_spi_cs->GPIO_Pin);
}
returnmessage->length;
};
然后,向RT-thread注册spi总线:
structstm32_spi_bus
{
structrt_spi_busparent;
SPI_TypeDefSPI;
#ifdefSPI_USE_DMA
DMA_Stream_TypeDefDMA_Stream_TX;
uint32_tDMA_Channel_TX;
DMA_Stream_TypeDefDMA_Stream_RX;
uint32_tDMA_Channel_RX;
uint32_tDMA_Channel_TX_FLAG_TC;
uint32_tDMA_Channel_RX_FLAG_TC;#endif/#ifdefSPI_USE_DMA/
};
structstm32_spi_cs
{
GPIO_TypeDefGPIOx;
uint16_tGPIO_Pin;
};
rt_err_tstm32_spi_register(SPI_TypeDefSPI,
structstm32_spi_busstm32_spi,
constcharspi_bus_name)
{
if(SPI==SPI1)
{
stm32_spi->SPI=SPI1;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);//84MHZ
#ifdefSPI_USE_DMA
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);
/DMA2_Stream0DMA_Channel_3:SPI1_RX;DMA2_Stream2DMA_Channel_3:
SPI1_RX/
stm32_spi->DMA_Stream_RX=DMA2_Stream0;
stm32_spi->DMA_Channel_RX=DMA_Channel_3;
stm32_spi->DMA_Channel_RX_FLAG_TC=DMA_FLAG_TCIF0;
/DMA2_Stream3DMA_Channel_3:SPI1_TX;DMA2_Stream5DMA_Channel_3:
SPI1_TX/
stm32_spi->DMA_Stream_TX=DMA2_Stream3;
stm32_spi->DMA_Channel_TX=DMA_Channel_3;
stm32_spi->DMA_Channel_TX_FLAG_TC=DMA_FLAG_TCIF3;#endif
}
elseif(SPI==SPI2)
{
stm32_spi->SPI=SPI2;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2,ENABLE);//42MHZ
#ifdefSPI_USE_DMA
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE);
/DMA1_Stream3DMA_Channel_0:SPI2_RX/
stm32_spi->DMA_Stream_RX=DMA1_Stream3;
stm32_spi->DMA_Channel_RX=DMA_Channel_0;
stm32_spi->DMA_Channel_RX_FLAG_TC=DMA_FLAG_TCIF3;
/DMA1_Stream4DMA_Channel_0:SPI2_TX/
stm32_spi->DMA_Stream_TX=DMA1_Stream4;
stm32_spi->DMA_Channel_TX=DMA_Channel_0;
stm32_spi->DMA_Channel_TX_FLAG_TC=DMA_FLAG_TCIF4;#endif
}
elseif(SPI==SPI3)
{
stm32_spi->SPI=SPI3;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI3,ENABLE);//42MHZ
#ifdefSPI_USE_DMA
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE);
/DMA1_Stream2DMA_Channel_0:SPI3_RX;DMA1_Stream0
DMA_Channel_0:SPI3_RX/
stm32_spi->DMA_Stream_RX=DMA1_Stream2;
stm32_spi->DMA_Channel_RX=DMA_Channel_0;
stm32_spi->DMA_Channel_RX_FLAG_TC=DMA_FLAG_TCIF2;
/DMA1_Stream5DMA_Channel_0:SPI3_TX;DMA1_Stream7
DMA_Channel_0:SPI3_TX/
stm32_spi->DMA_Stream_TX=DMA1_Stream5;
stm32_spi->DMA_Channel_TX=DMA_Channel_0;
stm32_spi->DMA_Channel_TX_FLAG_TC=DMA_FLAG_TCIF5;#endif
}
else
{
returnRT_ENOSYS;
}
returnrt_spi_bus_register(&stm32_spi->parent,spi_bus_name,
&stm32_spi_ops);
}
最后,进行spi硬件初始化,并挂载spi设备到已注册的spi总线。
intrt_hw_spi1_init(void)
{
/registerSPIbus/
staticstructstm32_spi_busstm32_spi;//itmustbeaddstatic
/SPI1configure/
{
GPIO_InitTypeDefGPIO_InitStructure;
/EnableGPIOPeriphclock/
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource5,GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource6,GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource7,GPIO_AF_SPI1);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_NOPULL;
/ConfigureSPI1pins/
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_5;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_6;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_7;
GPIO_Init(GPIOA,&GPIO_InitStructure);
}/SPI1configuration/
/registerSPI1tostm32_spi_bus/
stm32_spi_register(SPI1,&stm32_spi,"spi1");
/attachspi10/
{
staticstructrt_spi_devicert_spi_device_10;//itmustbeaddstatic
staticstructstm32_spi_csstm32_spi_cs_10;//itmustbeaddstatic
stm32_spi_cs_10.GPIOx=GPIOE;
stm32_spi_cs_10.GPIO_Pin=GPIO_Pin_3;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE,ENABLE);
GPIO_InitTypeDefGPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_NOPULL;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3;
GPIO_Init(GPIOE,&GPIO_InitStructure);
GPIO_SetBits(GPIOE,GPIO_Pin_3);
rt_spi_bus_attach_device(&rt_spi_device_10,"spi10","spi1",
(void)&stm32_spi_cs_10);//setspi_device->bus
/configspi/
{
structrt_spi_configurationcfg;
cfg.data_width=8;
cfg.mode=RT_SPI_MODE_3|RT_SPI_MSB;/SPICompatibleModes3and
SPI_FirstBit_MSBinlis302dldatasheet/
//APB2=168M/2=84M,SPI1=84/2,4,8,16,32=42M,21M,10.5M,5.25M,
2.625M...
cfg.max_hz=2625000;/SPI_BaudRatePrescaler_16=84000000/16=5.25MHz.
Themax_hzoflis302dlis10MHzindatasheet/
rt_spi_configure(&rt_spi_device_10,&cfg);
}/configspi/
}/attachspi10/
return0;
}
INIT_BOARD_EXPORT(rt_hw_spi1_init);//rt_hw_spi1_initwillbecalledin
rt_components_board_init()
三、SPI设备初始化
这里以lis302dl三轴加速度计为例:
staticrt_err_tlis302dl_init(constcharspi_device_name)
{
rt_uint8_tchip_id,ctrl,temp;
spi_device=(structrt_spi_device)rt_device_find(spi_device_name);
if(spi_device==RT_NULL)
{
rt_kprintf("\nspi_device%sforlis302dlnotfound!\n",spi_device_name);
return-RT_ENOSYS;
}
///Ifnotusert_device_writeorrt_device_read,thenit''snonecessary
tort_device_open////oflaghasnomeaningforspidevice,sosettoRT_NULL
///if(rt_device_open(&spi_device->parent,RT_NULL)!=RT_EOK)//{//
rt_kprintf("\nspi_device%sforlis302dlopenedfailed!\n",spi_device_name);//
return-RT_EEMPTY;//}
LIS302DL_InitTypeDefLIS302DL_InitStruct;
LIS302DL_FilterConfigTypeDefLIS302DL_FilterStruct;
/SetconfigurationofLIS302DL/
LIS302DL_InitStruct.Output_DataRate=LIS302DL_DATARATE_100;
LIS302DL_InitStruct.Power_Mode=LIS302DL_LOWPOWERMODE_ACTIVE;
LIS302DL_InitStruct.Full_Scale=LIS302DL_FULLSCALE_2_3;
LIS302DL_InitStruct.Self_Test=LIS302DL_SELFTEST_NORMAL;
LIS302DL_InitStruct.Axes_Enable=LIS302DL_XYZ_ENABLE;
LIS302DL_Init(&LIS302DL_InitStruct);
/MEMSHighPassFilterconfiguration/
LIS302DL_FilterStruct.HighPassFilter_Data_Selection=
LIS302DL_FILTEREDDATASELECTION_OUTPUTREGISTER;
LIS302DL_FilterStruct.HighPassFilter_Interrupt=
LIS302DL_HIGHPASSFILTERINTERRUPT_1_2;
LIS302DL_FilterStruct.HighPassFilter_CutOff_Frequency=
LIS302DL_HIGHPASSFILTER_LEVEL_1;
LIS302DL_FilterConfig(&LIS302DL_FilterStruct);
/notuseinternalhighpassfilterandINT2/
ctrl=0x04;//enableINT1Datareadyinterrupt;interruptactivehigh;pull-push;
LIS302DL_Write(&ctrl,LIS302DL_CTRL_REG3_ADDR,1);
LIS302DL_Read(&temp,LIS302DL_CTRL_REG3_ADDR,1);
if(temp==ctrl)
rt_kprintf("theLIS302DL_CTRL_REG3_ADDR(value0x%02x)verifypassed!\n",
temp);
else
rt_kprintf("theLIS302DL_CTRL_REG3_ADDR(value0x%02x)verifyfailed!\n",
temp);
/RequireddelayfortheMEMSAccelerometre:Turn-ontime=3/OutputdataRate
=3/100=30msindatasheet/
//rt_thread_delay(30);
externvoidstm32_mdelay(rt_uint32_tms);
stm32_mdelay(30);
/power_modeisactive/
LIS302DL_Read(&chip_id,LIS302DL_WHO_AM_I_ADDR,1);
rt_kprintf("(chip_idoflis302dlis0x%02x)",chip_id);
return0;
}
intrt_lis302dl_init(void)
{
rt_sem_init(&sem_lis302dl,"lis302dl",0,RT_IPC_FLAG_FIFO);
lis302dl_interrupt_int1();
lis302dl_init("spi10");
return0;
}
INIT_APP_EXPORT(rt_lis302dl_init);
注意事项:
1、若需要使用rt_device_read()或rt_device_write()函数,则必须先调用
rt_device_open()打开spi设备,保证该设备的ref_count大于0。硬件初始化函数中不
需要调用rt_device_open()打开spi总线,因为在rt_spi_bus_attach_device()函数中
没有初始化bus->owner,从而会导致调用_spi_bus_device_read()或
_spi_bus_device_write()时“RT_ASSERT(bus->owner!=RT_NULL);”断言语句进
入死循环。而_spidev_device_read()或_spidev_device_write()中断言语句
“RT_ASSERT(device->bus!=RT_NULL);”正常通过。
2、在使用SPI设备驱动操作数字芯片的寄存器时,需谨慎使用rt_device_read()和
rt_device_write()函数。因为根据spi读写时序,spi读写一次最少要连续操作2个字节
数据(第一个为寄存器地址值,第二个为待读取或待写入的字节数据),并且在这2个字
节数据之间CS信号不能拉高,而rt_device_read()和rt_device_write()函数仅操作一
个字节后,cs信号拉高,导致字节数据不能正常读取或写入相应寄存器。所以,一般情况
下在SPI工作在全双工模式时,读写数字芯片寄存器的函数中直接使用spi_core.c中的
rt_spi_transfer()、rt_spi_send_then_recv()、rt_spi_send_then_send()三个函数,
如下所示:
按Ctrl+C复制代码
按Ctrl+C复制代码
3、对于可读取寄存器值的数字芯片,在写入字节数据后可通过读取相同寄存器,判断读出
的值与写入的值是否一致,从而判断寄存器写操作是否正确,如下:
voidLIS302DL_Init(LIS302DL_InitTypeDefLIS302DL_InitStruct)
{
rt_uint8_tctrl=0x00;
/ConfigureMEMS:datarate,powermode,fullscale,selftestandaxes/
ctrl=(rt_uint8_t)(LIS302DL_InitStruct->Output_DataRate|
LIS302DL_InitStruct->Power_Mode|\
LIS302DL_InitStruct->Full_Scale|
LIS302DL_InitStruct->Self_Test|\
LIS302DL_InitStruct->Axes_Enable);
/WritevaluetoMEMSCTRL_REG1regsister/
LIS302DL_Write(&ctrl,LIS302DL_CTRL_REG1_ADDR,1);
rt_uint8_ttemp=0x00;
LIS302DL_Read(&temp,LIS302DL_CTRL_REG1_ADDR,1);
if(temp==ctrl)
rt_kprintf("\ntheLIS302DL_CTRL_REG1_ADDR(value0x%02x)verify
passed!\n",temp);
else
rt_kprintf("\ntheLIS302DL_CTRL_REG1_ADDR(value0x%02x)verify
failed!\n",temp);
}
RT-thread设备驱动组件之IIC总线设备
本文主要介绍RT-thread中IIC总线设备驱动,涉及到的主要文件有:驱动框架文
件(i2c_core.c,i2c_dev.c,i2c-bit-ops.c,i2c_dev.h,i2c.h);底层硬件驱动文件
(i2c_soft.c,i2c_soft.h)。这里的i2c_soft.c和i2c_soft.h是指利用MCU的GPIO
口模拟IIC总线时序,而不是利用MCU的硬件IIC接口。应用IIC总线设备驱动时,需要
在rtconfig.h中添加宏定义#defineRT_USING_I2C。若使用GPIO口模拟IIC总线,
则还需要添加宏定义#defineRT_USING_I2C_BITOPS。
RT-Thread的I2C驱动框架共分三层:
?I2C控制器驱动层
?I2C驱动抽象层
?统一设备操作接口层。
一、IIC总线设备驱动框架
先看i2c.h中定义的一些数据结构:
#defineRT_I2C_WR0x0000
#defineRT_I2C_RD(1u<<0)
#defineRT_I2C_ADDR_10BIT(1u<<2)/thisisatenbitchip
address/
#defineRT_I2C_NO_START(1u<<4)
#defineRT_I2C_IGNORE_NACK(1u<<5)
#defineRT_I2C_NO_READ_ACK(1u<<6)/whenI2Creading,wedonot
ACK/
structrt_i2c_msg
{
rt_uint16_taddr;
rt_uint16_tflags;
rt_uint16_tlen;
rt_uint8_tbuf;
};
structrt_i2c_bus_device;
structrt_i2c_bus_device_ops
{
rt_size_t(master_xfer)(structrt_i2c_bus_devicebus,
structrt_i2c_msgmsgs[],
rt_uint32_tnum);
rt_size_t(slave_xfer)(structrt_i2c_bus_devicebus,
structrt_i2c_msgmsgs[],
rt_uint32_tnum);
rt_err_t(i2c_bus_control)(structrt_i2c_bus_devicebus,
rt_uint32_t,
rt_uint32_t);
};
/fori2cbusdriver/
structrt_i2c_bus_device
{
structrt_deviceparent;
conststructrt_i2c_bus_device_opsops;
rt_uint16_tflags;
rt_uint16_taddr;
structrt_mutexlock;
rt_uint32_ttimeout;
rt_uint32_tretries;
voidpriv;
};
i2c_dev.h中相关数据结构(structrt_i2c_priv_data用于i2c_bus_device_control()
函数中RT_I2C_DEV_CTRL_RW控制标志):
#defineRT_I2C_DEV_CTRL_10BIT0x20
#defineRT_I2C_DEV_CTRL_ADDR0x21
#defineRT_I2C_DEV_CTRL_TIMEOUT0x22
#defineRT_I2C_DEV_CTRL_RW0x23
structrt_i2c_priv_data
{
structrt_i2c_msgmsgs;
rt_size_tnumber;
};
i2c-bit-ops.h中主要定义了模拟IIC总线时序时需要的数据结构:
structrt_i2c_bit_ops
{
voiddata;/privatedataforlowlevelroutines/
void(set_sda)(voiddata,rt_int32_tstate);
void(set_scl)(voiddata,rt_int32_tstate);
rt_int32_t(get_sda)(voiddata);
rt_int32_t(get_scl)(voiddata);
void(udelay)(rt_uint32_tus);
rt_uint32_tdelay_us;/sclandsdalinedelay/
rt_uint32_ttimeout;/intick/
};
在i2c_dev.c主要实现IIC设备驱动统一接口函数:i2c_bus_device_read(),
i2c_bus_device_write(),i2c_bus_device_control()以及
rt_i2c_bus_device_device_init()。
rt_err_trt_i2c_bus_device_device_init(structrt_i2c_bus_devicebus,
constchar
name)
{
structrt_devicedevice;
RT_ASSERT(bus!=RT_NULL);
device=&bus->parent;
device->user_data=bus;
/setdevicetype/
device->type=RT_Device_Class_I2CBUS;
/initializedeviceinterface/
device->init=RT_NULL;
device->open=RT_NULL;
device->close=RT_NULL;
device->read=i2c_bus_device_read;
device->write=i2c_bus_device_write;
device->control=i2c_bus_device_control;
/registertodevicemanager/
rt_device_register(device,name,RT_DEVICE_FLAG_RDWR);
returnRT_EOK;
}
i2c_core.c中实现IIC总线设备注册,以及使用IIC总线进行数据传输,如:
rt_i2c_transfer(),rt_i2c_master_send(),rt_i2c_master_recv()。
rt_err_trt_i2c_bus_device_register(structrt_i2c_bus_devicebus,
constchar
bus_name)
{
rt_err_tres=RT_EOK;
rt_mutex_init(&bus->lock,"i2c_bus_lock",RT_IPC_FLAG_FIFO);
if(bus->timeout==0)bus->timeout=RT_TICK_PER_SECOND;
res=rt_i2c_bus_device_device_init(bus,bus_name);
i2c_dbg("I2Cbus[%s]registered\n",bus_name);
returnres;
}
i2c-bit-ops.c中主要实现了利用GPIO模拟IIC总线时序的相关接口函数,如:
i2c_start(),i2c_restart(),i2c_stop(),i2c_waitack(),i2c_writeb(),i2c_readb(),
i2c_send_bytes(),i2c_send_ack_or_nack(),i2c_recv_bytes(),
i2c_send_address(),i2c_bit_send_address()等。并且实现了i2c_bit_xfer():
staticconststructrt_i2c_bus_device_opsi2c_bit_bus_ops=
{
i2c_bit_xfer,
RT_NULL,
RT_NULL
};
rt_err_trt_i2c_bit_add_bus(structrt_i2c_bus_devicebus,
constcharbus_name)
{
bus->ops=&i2c_bit_bus_ops;
returnrt_i2c_bus_device_register(bus,bus_name);
}
二、底层硬件驱动
本文采用的是模拟IIC,即用GPIO口模拟IIC时序。在i2c_soft.c中主要实现struct
rt_i2c_bit_ops中的指针函数:
voidstm32_set_sda(voiddata,rt_int32_tstate)
{
if(state==1)
GPIO_SetBits(I2C1_GPIO,I2C1_GPIO_SDA);//GPIOB->BSRRL=
I2C1_GPIO_SDA
elseif(state==0)
GPIO_ResetBits(I2C1_GPIO,I2C1_GPIO_SDA);//GPIOB->BSRRH=
I2C1_GPIO_SDA
}
voidstm32_set_scl(voiddata,rt_int32_tstate)
{
if(state==1)
GPIO_SetBits(I2C1_GPIO,I2C1_GPIO_SCL);//GPIOB->BSRRL=
I2C1_GPIO_SCL
elseif(state==0)
GPIO_ResetBits(I2C1_GPIO,I2C1_GPIO_SCL);//GPIOB->BSRRH=
I2C1_GPIO_SCL
}
rt_int32_tstm32_get_sda(voiddata)
{
return(rt_int32_t)GPIO_ReadInputDataBit(I2C1_GPIO,
I2C1_GPIO_SDA);//return(GPIOB->IDR&I2C1_GPIO_SDA)
}
rt_int32_tstm32_get_scl(voiddata)
{
return(rt_int32_t)GPIO_ReadInputDataBit(I2C1_GPIO,
I2C1_GPIO_SCL);//return(GPIOB->IDR&I2C1_GPIO_SCL)
}
voidstm32_udelay(rt_uint32_tus)
{
rt_uint32_tdelta;
/????us?óê±?ùDè??êy?μ£?sysTick->LOAD=21000,
RT_TICK_PER_SECOND=1000/
us=us(SysTick->LOAD/(1000000/RT_TICK_PER_SECOND));
/??è?μ±?°à?àa??êy?μ/
delta=SysTick->VAL;
/?óê±us/
while(delta-SysTick->VAL }
voidstm32_mdelay(rt_uint32_tms)
{
stm32_udelay(ms1000);
}
staticconststructrt_i2c_bit_opsstm32_i2c_bit_ops=
{
(void)0xaa,//nouseinset_sda,set_scl,get_sda,get_scl
stm32_set_sda,
stm32_set_scl,
stm32_get_sda,
stm32_get_scl,
stm32_udelay,
20,
5
};
最后,实现IIC总线硬件初始化(包括RCC时钟配置和GPIO配置,最重要的是将
stm32_i2c_bit_ops初始化为IIC总线设备结构体的priv变量,即stm32_i2c.priv=
(void)&stm32_i2c_bit_ops):
intrt_hw_i2c_init(void)
{
staticstructrt_i2c_bus_devicestm32_i2c;//"static"addbyme.It
mustbeadd"static",oritwillbehardfault
RCC_Configuration();
GPIO_Configuration();
rt_memset((void)&stm32_i2c,0,sizeof(structrt_i2c_bus_device));
stm32_i2c.priv=(void)&stm32_i2c_bit_ops;
rt_i2c_bit_add_bus(&stm32_i2c,"i2c1");
return0;
}
INIT_BOARD_EXPORT(rt_hw_i2c_init);//rt_hw_i2c_initwillbecalledin
rt_components_board_init()
三、IIC总线设备初始化
这里以cs43l22数字音频放大器为例:
staticrt_err_tcs43l22_init(constchari2c_bus_name)
{
i2c_bus=(structrt_i2c_bus_device)rt_device_find(i2c_bus_name);
if(i2c_bus==RT_NULL)
{
rt_kprintf("\ni2c_bus%sforcs43l22notfound!\n",i2c_bus_name);
return-RT_ENOSYS;
}
/oflaghasnomeaningforspidevice,sosettoRT_NULL/
if(rt_device_open(&i2c_bus->parent,RT_NULL)!=RT_EOK)
{
rt_kprintf("\ni2c_bus%sforcs43l22openedfailed!\n",
i2c_bus_name);
return-RT_EEMPTY;
}
EVAL_AUDIO_Init(OUTPUT_DEVICE_AUTO,volume,I2S_AudioFreq_48k);
/itmustbeatthebackofEVAL_AUDIO_Init,whichresetthecs43l22
/
uint8_tchip_id=Codec_ReadRegister(i2c_bus,0x01);
rt_kprintf("(chip_idofcs43l22is0x%02x)",chip_id);
return0;
}
intrt_cs43l22_init(void)
{
rt_sem_init(&sem_cs43l22,"cs43l22",1,RT_IPC_FLAG_FIFO);
cs43l22_init("i2c1");
return0;
}
INIT_APP_EXPORT(rt_cs43l22_init);
注意事项:
1、在应用IIC总线设备驱动时,需要用到rt_device_read或rt_device_write,因此在
初始化函数中需要调用rt_device_open将IIC总线设备打开。
2、下面利用rt_device_read和rt_device_write操作寄存器(每一次调用
rt_device_read或rt_device_write都包括了i2c_start,i2c_bit_send_address,
i2c_recv_bytes/i2c_send_bytes,i2c_stop这4个步骤):
staticuint32_tCodec_WriteRegister(structrt_i2c_bus_devicei2c_bus,
uint8_tRegisterAddr,uint8_tRegisterValue)
{
uint32_tresult=0;
rt_uint16_tflags=0x00;
rt_uint16_tDevAddr=(rt_uint16_t)CODEC_ADDRESS>>1;
rt_off_tpos=(rt_off_t)((flags<<16)|DevAddr);
rt_uint8_tbuffer[2];
buffer[0]=RegisterAddr;
buffer[1]=RegisterValue;
rt_device_write(&i2c_bus->parent,pos,buffer,sizeof(buffer));
#ifdefVERIFY_WRITTENDATA
/Verifythatthedatahasbeencorrectlywritten/
result=(Codec_ReadRegister(i2c_bus,RegisterAddr)==RegisterValue)?
0:1;
if(result==0)
rt_kprintf("\nthereg0x%02xverifypassed\n",RegisterAddr);
else
rt_kprintf("\nthereg0x%02xverifyfailed\n",RegisterAddr);
#endif/VERIFY_WRITTENDATA/
/Returntheverifyingvalue:0(Passed)or1(Failed)/
returnresult;
}
staticuint8_tCodec_ReadRegister(structrt_i2c_bus_devicei2c_bus,
uint8_tRegisterAddr)
{
rt_uint16_tflags=0x00;
rt_uint16_tDevAddr=(rt_uint16_t)CODEC_ADDRESS>>1;
rt_off_tpos=(rt_off_t)((flags<<16)|DevAddr);
rt_uint8_tbuffer;
buffer=RegisterAddr;
rt_device_write(&i2c_bus->parent,pos,&buffer,1);
rt_device_read(&i2c_bus->parent,pos,&buffer,1);
/ReturnthebytereadfromCodec/
returnbuffer;
}
在上面两个函数中,有符号整型32位pos的高16位表示flags,低16位表示IIC器件
地址。flags取值如i2c.h文件中宏定义所示。
这里说明一个问题:在rt_i2c_master_send和rt_i2c_master_recv函数中均有
“msg.flags=flags&RT_I2C_ADDR_10BIT;”这一语句,该句用于标志IIC器件地址
是否为10位地址,但是这条语句会将其他预置好的标志全部清除,如
RT_I2C_NO_START,RT_I2C_IGNORE_NACK或RT_I2C_NO_READ_ACK。所以,
在一般情况下,flags标志只能事先预置RT_I2C_ADDR_10BIT,若IIC器件地址为7位,
则直接设置flags为0。
3、根据i2c_bit_send_address()函数中:
else
{
/7-bitaddr/
addr1=msg->addr<<1;
if(flags&RT_I2C_RD)
addr1|=1;
ret=i2c_send_address(bus,addr1,retries);
if((ret!=1)&&!ignore_nack)
return-RT_EIO;
}
RT-threadfinsh组件工作流程
finsh是RT-Thread的命令行外壳(shell),提供一套供用户在命令行的操作接口,主要
用于调试、查看系统信息。在大部分嵌入式系统中,一般开发调试都使用硬件调试器和printf
日志打印,在有些情况下,这两种方式并不是那么好用。比如对于RT-Thread这个多线程
系统,我们想知道某个时刻系统中的线程运行状态、手动控制系统状态。如果有一个shell,
就可以输入命令,直接相应的函数执行获得需要的信息,或者控制程序的行为。这无疑会十
分方便。
finsh支持两种模式:
1.C语言解释器模式,为行文方便称之为c-style;
2.传统命令行模式,此模式又称为msh(moduleshell)。C语言表达式解释模式下,finsh
能够解析执行大部分C语言的表达式,并使用类似C语言的函数调用方式访问系统中的函
数及全局变量,此外它也能够通过命令行方式创建变量。在msh模式下,finsh运行方式
类似于dos/bash等传统shell。
大致工作流程
一、finsh组件初始化函数finsh_system_init(),并且添加了
INIT_COMPONENT_EXPORT(finsh_system_init),支持组件初始化;
这个函数会初始化finsh组件,包括一些finsh变量以及相关数据结构。
然后它会创建一个线程,代码如下:
result=rt_thread_init(&finsh_thread,
"tshell",
finsh_thread_entry,RT_NULL,
&finsh_thread_stack[0],sizeof(finsh_thread_stack),
FINSH_THREAD_PRIORITY,10);
if(result==RT_EOK)
rt_thread_startup(&finsh_thread);
可以看到,线程函数是finsh_thread_entry,在下一节中我们将分析它具体工作流程。
二、voidfinsh_set_device(constchardevice_name)函数为finsh设置终端设备,
在stm32中主要设置串口设备为终端。该函数一般放在组件初始化函数
rt_component_init()后面,因为要先完成finsh组件初始化才能设置终端设备。
voidfinsh_set_device(constchardevice_name)
{
rt_device_tdev=RT_NULL;
RT_ASSERT(shell!=RT_NULL);
dev=rt_device_find(device_name);
if(dev==RT_NULL)
{
rt_kprintf("finsh:cannotfinddevice:%s\n",device_name);
return;
}
/checkwhetherit''sasamedevice/
if(dev==shell->device)return;
/openthisdeviceandsetthenewdeviceinfinshshell/
if(rt_device_open(dev,RT_DEVICE_OFLAG_RDWR|
RT_DEVICE_FLAG_INT_RX|\
RT_DEVICE_FLAG_STREAM)==RT_EOK)
{
if(shell->device!=RT_NULL)
{
/closeoldfinshdevice/
rt_device_close(shell->device);
rt_device_set_rx_indicate(shell->device,RT_NULL);
}
shell->device=dev;
rt_device_set_rx_indicate(dev,finsh_rx_ind);
}
}
这个函数为finsh组件设置使用的串口,从这个函数中我们可以总结出,如何使用串口设备。
1.调用rt_device_find使用设备的字符串名字查找设备,得到设备数据结构指针
2.调用rt_devcie_open打开设备,open_flag为RT_DEVICE_OFLAG_RDWR|
RT_DEVICE_FLAG_INT_RX|RT_DEVICE_FLAG_STREAM
对finsh来说,还使用了rt_device_set_rx_indicate函数设置了一个回调函数
finsh_rx_ind,它的作用我们后面会讨论
到这里设备就被打开了。
在serial.c中rt_hw_serial_isr()中有:
/invokecallback/
if(serial->parent.rx_indicate!=RT_NULL)
{
rt_size_trx_length;
/getrxlength/
level=rt_hw_interrupt_disable();
rx_length=(rx_fifo->put_index>=rx_fifo->get_index)?
(rx_fifo->put_index-rx_fifo->get_index):
(serial->config.bufsz-(rx_fifo->get_index-
rx_fifo->put_index));
rt_hw_interrupt_enable(level);
serial->parent.rx_indicate(&serial->parent,
rx_length);
}
上面计算得到rx_length,然后触发回调函数,也就是前面的finsh_rx_ind函数,即实际
执行的是fins_rx_ind(device,rx_length)。
在shell.c中fins_rx_ind源码为:
staticrt_err_tfinsh_rx_ind(rt_device_tdev,rt_size_tsize)
{
RT_ASSERT(shell!=RT_NULL);
/releasesemaphoretoletfinshthreadrxdata/
rt_sem_release(&shell->rx_sem);
returnRT_EOK;
}
这个函数里只是简单的释放信号量。也就是说,当串口硬件上接收到一个字节,就会调用
finsh_rx_ind函数来释放一个信号量。
三、finsh线程函数的工作流程概述
voidfinsh_thread_entry(voidparameter)
{
charch;
/normalisechomode/
shell->echo_mode=1;
#ifndefFINSH_USING_MSH_ONLY
finsh_init(&shell->parser);
#endif
rt_kprintf(FINSH_PROMPT);
/setconsoledeviceasshelldevice/
if(shell->device==RT_NULL)
{
#ifdefRT_USING_CONSOLE
shell->device=rt_console_get_device();
RT_ASSERT(shell->device);
rt_device_set_rx_indicate(shell->device,finsh_rx_ind);
rt_device_open(shell->device,(RT_DEVICE_OFLAG_RDWR|
RT_DEVICE_FLAG_STREAM|RT_DEVICE_FLAG_INT_RX));
#else
RT_ASSERT(shell->device);
#endif
}
while(1)
{
/waitreceive/
if(rt_sem_take(&shell->rx_sem,RT_WAITING_FOREVER)!=RT_EOK)
continue;
/readonecharacterfromdevice/
while(rt_device_read(shell->device,0,&ch,1)==1)
{
/
handlecontrolkey
upkey:0x1b0x5b0x41
downkey:0x1b0x5b0x42
rightkey:0x1b0x5b0x43
leftkey:0x1b0x5b0x44
/
if(ch==0x1b)
{
shell->stat=WAIT_SPEC_KEY;
continue;
}
elseif(shell->stat==WAIT_SPEC_KEY)
{
if(ch==0x5b)
{
shell->stat=WAIT_FUNC_KEY;
continue;
}
shell->stat=WAIT_NORMAL;
}
elseif(shell->stat==WAIT_FUNC_KEY)
{
shell->stat=WAIT_NORMAL;
if(ch==0x41)/upkey/
{
#ifdefFINSH_USING_HISTORY
/prevhistory/
if(shell->current_history>0)
shell->current_history--;
else
{
shell->current_history=0;
continue;
}
/copythehistorycommand/
memcpy(shell->line,
&shell->cmd_history[shell->current_history][0],
FINSH_CMD_SIZE);
shell->line_curpos=shell->line_position=
strlen(shell->line);
shell_handle_history(shell);
#endif
continue;
}
elseif(ch==0x42)/downkey/
{
#ifdefFINSH_USING_HISTORY
/nexthistory/
if(shell->current_historyhistory_count-
1)
shell->current_history++;
else
{
/settotheendofhistory/
if(shell->history_count!=0)
shell->current_history=
shell->history_count-1;
else
continue;
}
memcpy(shell->line,
&shell->cmd_history[shell->current_history][0],
FINSH_CMD_SIZE);
shell->line_curpos=shell->line_position=
strlen(shell->line);
shell_handle_history(shell);
#endif
continue;
}
elseif(ch==0x44)/leftkey/
{
if(shell->line_curpos)
{
rt_kprintf("\b");
shell->line_curpos--;
}
continue;
}
elseif(ch==0x43)/rightkey/
{
if(shell->line_curposline_position)
{
rt_kprintf("%c",
shell->line[shell->line_curpos]);
shell->line_curpos++;
}
continue;
}
}
/handleCRkey/
if(ch==''\r'')
{
charnext;
if(rt_device_read(shell->device,0,&next,1)==1)
ch=next;
elsech=''\r'';
}
/handletabkey/
elseif(ch==''\t'')
{
inti;
/movethecursortothebeginningofline/
for(i=0;iline_curpos;i++)
rt_kprintf("\b");
/autocomplete/
shell_auto_complete(&shell->line[0]);
/re-calculateposition/
shell->line_curpos=shell->line_position=
strlen(shell->line);
continue;
}
/handlebackspacekey/
elseif(ch==0x7f||ch==0x08)
{
/notethatshell->line_curpos>=0/
if(shell->line_curpos==0)
continue;
shell->line_position--;
shell->line_curpos--;
if(shell->line_position>shell->line_curpos)
{
inti;
rt_memmove(&shell->line[shell->line_curpos],
&shell->line[shell->line_curpos+1],
shell->line_position-
shell->line_curpos);
shell->line[shell->line_position]=0;
rt_kprintf("\b%s\b",
&shell->line[shell->line_curpos]);
/movethecursortotheoriginposition/
for(i=shell->line_curpos;i<=
shell->line_position;i++)
rt_kprintf("\b");
}
else
{
rt_kprintf("\b\b");
shell->line[shell->line_position]=0;
}
continue;
}
/handleendofline,break/
if(ch==''\r''||ch==''\n'')
{
#ifdefFINSH_USING_HISTORY
shell_push_history(shell);
#endif
#ifdefFINSH_USING_MSH
if(msh_is_used()==RT_TRUE)
{
rt_kprintf("\n");
msh_exec(shell->line,shell->line_position);
}
else
#endif
{
#ifndefFINSH_USING_MSH_ONLY
/add'';''andrunthecommandline/
shell->line[shell->line_position]='';'';
if(shell->line_position!=0)
finsh_run_line(&shell->parser,shell->line);
elsert_kprintf("\n");
#endif
}
rt_kprintf(FINSH_PROMPT);
memset(shell->line,0,sizeof(shell->line));
shell->line_curpos=shell->line_position=0;
break;
}
/it''salargeline,discardit/
if(shell->line_position>=FINSH_CMD_SIZE)
shell->line_position=0;
/normalcharacter/
if(shell->line_curposline_position)
{
inti;
rt_memmove(&shell->line[shell->line_curpos+1],
&shell->line[shell->line_curpos],
shell->line_position-shell->line_curpos);
shell->line[shell->line_curpos]=ch;
if(shell->echo_mode)
rt_kprintf("%s",
&shell->line[shell->line_curpos]);
/movethecursortonewposition/
for(i=shell->line_curpos;iline_position;
i++)
rt_kprintf("\b");
}
else
{
shell->line[shell->line_position]=ch;
if(shell->echo_mode)
rt_kprintf("%c",ch);
}
ch=0;
shell->line_position++;
shell->line_curpos++;
if(shell->line_position>=80)
{
/clearcommandline/
shell->line_position=0;
shell->line_curpos=0;
}
}/endofdeviceread/
}
}
函数主体依然是一个while(1)循环,这是显然的,因为finsh要不停的监听终端上输入。
if(rt_sem_take(&shell->rx_sem,RT_WAITING_FOREVER)!=RT_EOK)
continue;
即,如果串口上没有收到任何数据,并且串口缓冲区中也无数据,即shell→rx_sem信号
量的值为0,那么这个函数会使finsh线程休眠,RTT内核会执行其他线程。
当串口收到数据,串口终端调用回调函数finsh_rx_ind函数来释放信号量,这会唤醒finsh
线程,rt_sem_take函数会执行完毕,继续执行接下来的代码。
接下来的代码调用rt_device_read函数从串口数据缓冲池中读取一个字节。
然后判断所读取的到这个字节(判断上下左右四个按键所代表的字节)。
(1)如果是''\r'',即表示用户按下了回车键,再调用rt_device_read函数来读取一个字节,
如果读到,则这将更新读到的字节,一般情况下,这个函数会返回0,即没有读到新的字节。
(2)如果是''\t'',即表示用户按下了TAB键,则调用finsh_auto_complete函数,这个函
数做自动补全操作,也就是根据当前已输入的字符串,从finsh内部已注册的函数/变量中
查找匹配字符串,如果找到则会在终端上自动补全。
(3)如果是0x7f或者0x08说明:查ascii码表可知,0x08表示按下了backspace键,
【0x7f表示按下了DEL键,这个不对劲,如何知道当我们按下了键盘按键时,串口都收到
了什么数据呢?】这表示用户期望删除已经输入的字符串,根据测试结果,发送”\0x08
\0x08”,可以实现退格。
(4)如果收到了''\r''或者''\n'',则表示用户按下了回车,希望处理这个命令,那么
finsh_run_line函数被执行,这个函数会从从finsh已注册的函数/变量中匹配当前从终端
里获取的字符串,如果匹配到,则执行对应的函数(若字符串为函数名)或者打印变量的值
(若字符串为已变量)。
(5)回显字符,也就是将刚才从串口接收到终端发送的字符发送到终端软件上显示出来。这
就是说,我们在终端软件上输入字符,并且可以看到我们输入的字符,实际上是板子上的串
口重新发回来显示的。在上面finsh的线程代码中,rt_device_write函数是在rt_kprintf
中调用的。
然后回到(1),重复这个过程。 |
|