RTThread上设备SPI移植与实践
RTThread上设备SPI移植与实践
作为通信协议的两大基础,IIC和SPI两者的应用都非常广泛,上一篇文章讲过了RTT上IIC的移植与实践,讲完IIC,自然少不了SPI的相关内容,基于此,本文就来详细说明RTThread上设备SPI的移植与实践,SPI的通信原理不用赘述,百度一搜索,对应的知识点就明明白白的。本文的主要内容还是阐述一下RTThread上SPI的一些细节内容,总体感觉RTThread的代码层次,以及各个设备驱动的核心文件都相当优秀,这个不是打广告,而是真正的谁用谁知道,随着慢慢地学习,也会慢慢爱上它,不得不对RTThread表示赞扬,的确优秀源自长时间的积累升华,好了,废话不闲叙,开始本文的主体介绍。
1、RTThread上SPI设备“上线”
这里的“上线”,你也许会好奇,这个只是我自己的理解,同RTThread设备IIC总线不一样的是,IIC总线注册了就可以直接使用,而SPI总线注册完了之后,相当于存在于内核中,还要进行一步,我所理解的“上线”得出操作,即SPI设备需要挂载到总线上面去,才可以进行相关操作。以下是代码里面的细节
注册:register函数,同其他设备注册到内核系统中是同样的套路
“上线”:bus _attach_device,设备挂载在总线上,占用总线,开始通信
以上也可以简单理解为一个流程,1,注册SPI总线,2相关硬件初始化,3设备挂载SPI总线上,4,通信开始
2、RTThread上设备SPI
主要文件分为3个部分,一个硬件驱动层:drv_spi.c,一个内核控制层spi_core.c,一个抽象层spi_dev.c。三个文件分工明确,结构都是比较清晰的,结合官方给的相关文档,参照rtthread设备驱动的相关架构,很容易理解这些文件中的相关驱动的内容,以下是相关工程文件
内核的相关内容和相关函数介绍说明,整体清晰,局部细节有内涵,总体感觉RTThread相当优秀,层次清晰明了,丝毫不脱离带水,值得学习研究,相关函数和功能细节也可以查询官方文档获取相关专业解释。
内核与硬件交互驱动也是比较简洁,一目了然,SPI也是一种通信协议,因此和IIC的相关主体也是一样,读、写、控制,较IIC驱动多了一个设备上总线的初始化相关的函数,主要是设备与总线的相关绑定内容。具体内容见下图。
3、RTThread设备SPI通信板级实践3.1、板级硬件
结合Demo板原理图,芯片的硬件SPI1总线挂载了两个设备一个是flash,GD25Q16,一个LCD,两个SPI设备通过不同的GPIO引脚去完成片选。后续的板级实现,主要是对Flash进行了SPI控制实践。
3.2、SPI实践
硬件SPI对应GPIO功能设置,自然这些都是按照规格书进行相关操作,GPIO相关AF功能的设置,对应的芯片DataSheet都有祥诉,可以去参考
SPI配置,包含SPI模式,SPI位宽,SPI最大传输频率等参数
3.3总线注册
如下图是RTThread发现的device,其中包含了IIC总线,SPI总线,对应GPIO设备
3.4、设备“上线”
“spi10”即“spi1”上的0号设备,查看list_device发现了新的设备“spi10”,这一个就是挂载在SPI总线上的Flash设备。
结合自己定义的SPI设备名称,(当然这里的名称是可以修改的),执行设备挂载到SPI总线上,就可以在device list中看到自己定义的SPI设备,结合以下两张图片就可以清晰的实践SPI总线、SPI设备的发现与“上线”。
3.5、读取SPI设备ChipID
通过相关地址读取FlashID,分享的例程,实现了两种SPI操作读取FlashID。
4、总结
通过学习和实践,越发觉得RTThread的优秀与不简单,路漫漫,依旧还是需要保持上下求索的心态,认真学习体会这一实时操作系统的魅力,后续也会坚持学以致用,文章肯定有许多不完善和表述不当的地方,还希望不吝赐教,谢谢,最后附上工程实践代码。
原标题: RTThread上设备SPI移植与实践
原作者: yanzhengxin1
本文为21ic有奖征文作品,详情请见21ic论坛活动专区:第二届万元红包——蓝V达人有奖征文活动,如果您也有兴趣参与征文,欢迎进入论坛参与活动~
SD nand 与 SD卡的SPI模式驱动
文章目录
SD nand 与 SD卡的SPI模式驱动
1. 概述
2. SPI接口模式与SD接口模式区别
2.1 接口模式区别
2.2 硬件引脚
2.3 注意事项
3. SD接口协议
3.1 命令
3.1.1 命令格式
3.1.2 命令类型
3.2 响应
3.2.1 响应格式
4. SD nand(SD卡)结构描述
5. SD nand SPI通讯
5.1 SD nand SPI 通讯概述
5.2 SPI 时序
5.3 上电初始化及模式切换
5.3.1 初始化及模式切换流程说明
5.3.2 代码实现
5.4 识别过程
5.4.1 识别流程说明
5.4.2 代码实现
5.3 数据传输
5.3.1 数据写入
5.3.2 数据读取
5.3.3 代码实现
6. 总结
1. 概述
首先简单介绍下SD卡和SD nand:
SD卡,也称之为内存卡,在早些年间的手机上出现过,主要用来存储数据;3.1 命令
命令由主机发出,分为广播命令和寻址命令广播命令是针对与SD主机连接的所有设备发出的寻址命令是指定某个地址的设备进行命令传输3.1.1 命令格式命令由48bit位(6字节)组成,格式如下:5. SD nand SPI通讯
主要参考资料:官方文档《Part_1_Pjysical_Layer_Specification_Ver2.0.0pdf》建议大家有时间的话也可以读一读,还是有收获的,如果没时间的话也可以先参考本博文5.1 SD nand SPI 通讯概述SD nand SPI通讯接口完成驱动主要可以分为三大部分:上电初始化以及模式切换SD nand(SD卡)识别数据传输两大步在以上三大部分中,每个部分均有命令传输,从3.1.1中我们可以知道发送给SD nand的命令为48bit,也就是8字节,那么SPI模式下与SD nand通讯,发送命令其实就是采用SPI总线往SD nand传输8个字节的数据,大家把握这这个思路去理解下文的通讯过程也就简单多了。需要注意的是:SD nand或SD卡上电默认均为SD模式,需要对齐完成初始化以及模式切换后才能切换到SPI模式。SD 模式,所有命令默认开启CRC校验,因此没有切换到SPI模式之前,所有命令都必须携带正确的CRC校验值进入SPI模式后,默认关闭CRC校验,此时CRC校验字段默认填充1即可,当然也可以通过命令配置打开SPI模式的CRC校验5.2 SPI 时序在开始进行通讯读写前,我们先来看下SPI时序,使用SPI完成于SD nand(SD卡)的通讯与我们平常使用SPI与其他设备通讯会有一点点小小的区别,主要在于往SD nand写了数据之后,回复不是马上的,以及在必要的数据之间需要增加间隔,我们挑几个重点看下,在实际开发中有需要注意的在后文对应处有描述,不用过于担心。1.主机发送命令给卡,卡响应,注意图中的NCR,NCR最小不是0,因此主机发送了命令之后,SD nand不是马上就响应的#include "./spi/bsp_spi.h"
/**
* @brief spi gpio configuration
*
* @note CLK:PA5 MISO:PA6 MOSI:PA7 CS:PA8
*
*/
static void _spi_gpio_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure = {0};
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
/* Configure SD_SPI pins: SCK */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* Configure SD_SPI pins: MOSI */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* Configure SD_SPI pins: MISO */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/*!< Configure SD_SPI_CS_PIN pin: SD Card CS pin */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
/**
* @brief configer spi1 peripher.
*
* @note Data rising edge acquisition.
*/
static void _spi_config(void)
{
SPI_InitTypeDef SPI_InitStructure = {0};
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
/*!< SD_SPI Config */
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 0;
SPI_Init(SPI1, &SPI_InitStructure);
SPI_Cmd(SPI1, ENABLE);
}
int sd_spi_config(void)
{
_spi_gpio_init();
_spi_config();
return 0;
}
void set_sd_spi_cs_pin(uint8_t state)
{
if (state)
GPIO_SetBits(GPIOA, GPIO_Pin_8);
else
GPIO_ResetBits(GPIOA, GPIO_Pin_8);
}
2.SD初始化代码如下,set_sd_to_idle_state 函数向SD nand发送CMD0指令,同时由于发送CMD0时,SD nand还处于SD模式,因此手动计算CRC结果为0x95并发送,发送完CMD0之后等待SD nand的R1响应,并根据响应内容,知道SD nand操作完成。
#ifndef __SD_SPI_DRV_H__
#define __SD_SPI_DRV_H__
#include "stm32f10x.h"
/**
* @brief Commands: CMDxx = CMD-number | 0x40
*/
#define SD_CMD_GO_IDLE_STATE 0 /*!< CMD0 = 0x40 */
#define SD_CMD_SEND_OP_COND 1 /*!< CMD1 = 0x41 */
#define SD_CMD_SEND_IF_COND 8 /*!< CMD8 = 0x48 */
#define SD_CMD_SEND_CSD 9 /*!< CMD9 = 0x49 */
#define SD_CMD_SEND_CID 10 /*!< CMD10 = 0x4A */
#define SD_CMD_STOP_TRANSMISSION 12 /*!< CMD12 = 0x4C */
#define SD_CMD_SEND_STATUS 13 /*!< CMD13 = 0x4D */
#define SD_CMD_SET_BLOCKLEN 16 /*!< CMD16 = 0x50 */
#define SD_CMD_READ_SINGLE_BLOCK 17 /*!< CMD17 = 0x51 */
#define SD_CMD_READ_MULT_BLOCK 18 /*!< CMD18 = 0x52 */
#define SD_CMD_SET_BLOCK_COUNT 23 /*!< CMD23 = 0x57 */
#define SD_CMD_WRITE_SINGLE_BLOCK 24 /*!< CMD24 = 0x58 */
#define SD_CMD_WRITE_MULT_BLOCK 25 /*!< CMD25 = 0x59 */
#define SD_CMD_PROG_CSD 27 /*!< CMD27 = 0x5B */
#define SD_CMD_SET_WRITE_PROT 28 /*!< CMD28 = 0x5C */
#define SD_CMD_CLR_WRITE_PROT 29 /*!< CMD29 = 0x5D */
#define SD_CMD_SEND_WRITE_PROT 30 /*!< CMD30 = 0x5E */
#define SD_CMD_SD_ERASE_GRP_START 32 /*!< CMD32 = 0x60 */
#define SD_CMD_SD_ERASE_GRP_END 33 /*!< CMD33 = 0x61 */
#define SD_CMD_UNTAG_SECTOR 34 /*!< CMD34 = 0x62 */
#define SD_CMD_ERASE_GRP_START 35 /*!< CMD35 = 0x63 */
#define SD_CMD_ERASE_GRP_END 36 /*!< CMD36 = 0x64 */
#define SD_CMD_UNTAG_ERASE_GROUP 37 /*!< CMD37 = 0x65 */
#define SD_CMD_ERASE 38 /*!< CMD38 = 0x66 */
#define SD_CMD_READ_OCR 58 /*!< CMD58 */
#define SD_CMD_APP_CMD 55 /*!< CMD55 返回0x01*/
#define SD_ACMD_SD_SEND_OP_COND 41 /*!< ACMD41 返回0x00*/
typedef enum {
/**
* @brief SD reponses and error flags
*/
SD_RESPONSE_NO_ERROR = (0x00),
SD_IN_IDLE_STATE = (0x01),
SD_ERASE_RESET = (0x02),
SD_ILLEGAL_COMMAND = (0x04),
SD_COM_CRC_ERROR = (0x08),
SD_ERASE_SEQUENCE_ERROR = (0x10),
SD_ADDRESS_ERROR = (0x20),
SD_PARAMETER_ERROR = (0x40),
SD_RESPONSE_FAILURE = (0xFF),
/**
* @brief Data response error
*/
SD_DATA_OK = (0x05),
SD_DATA_CRC_ERROR = (0x0B),
SD_DATA_WRITE_ERROR = (0x0D),
SD_DATA_OTHER_ERROR = (0xFF)
} SD_ERROR;
//SD卡的类型
#define SD_TYPE_NOT_SD 0 //非SD卡
#define SD_TYPE_V1 1 //V1.0的卡
#define SD_TYPE_V2 2 //SDSC
#define SD_TYPE_V2HC 4 //SDHC
extern uint8_t SD_Type;
void sd_power_on(void);
SD_ERROR set_sd_to_idle_state(void);
SD_ERROR get_sd_card_type(void);
#endif /* __SD_SPI_DRV_H__ */
#include "./sd_nand/sd_spi_drv.h"
#include "./spi/bsp_spi.h"
#define SD_SPI SPI1
#define SD_DUMMY_BYTE 0xFF
uint8_t SD_Type = 0;
static uint8_t _spi_read_write_byte(uint8_t data)
{
while(SPI_I2S_GetFlagStatus(SD_SPI, SPI_I2S_FLAG_TXE) == RESET);
SPI_I2S_SendData(SD_SPI, data);
while(SPI_I2S_GetFlagStatus(SD_SPI, SPI_I2S_FLAG_RXNE) == RESET);
return SPI_I2S_ReceiveData(SD_SPI);
}
static void sd_send_cmd(uint8_t cmd, uint32_t arg, uint8_t crc)
{
uint8_t data[6] = {0};
/* command bit7 is always 1, bit6 is always 0, see SD manual. */
data[0] &= ~(0x80);
data[0] = cmd | 0x40;
data[1] = (uint8_t)(arg >> 24);
data[2] = (uint8_t)(arg >> 16);
data[3] = (uint8_t)(arg >> 8);
data[4] = (uint8_t)(arg);
data[5] = crc;
for (int i = 0; i < 6; i ++)
_spi_read_write_byte(data[i]);
}
static uint8_t sd_read_response(uint8_t response)
{
uint32_t repeat = 0xfff;
while (repeat --) {
if (_spi_read_write_byte(SD_DUMMY_BYTE) == response)
break;
}
if (repeat)
return SD_RESPONSE_NO_ERROR;
else
return SD_RESPONSE_FAILURE;
}
void sd_power_on(void)
{
set_sd_spi_cs_pin(PIN_HIGH);
uint32_t i = 0;
for (i = 0; i <= 9; i++) {
_spi_read_write_byte(SD_DUMMY_BYTE);
}
}
SD_ERROR set_sd_to_idle_state(void)
{
uint32_t repeat = 0xfff;
set_sd_spi_cs_pin(PIN_LOW);
sd_send_cmd(SD_CMD_GO_IDLE_STATE, 0, 0x95);
if (sd_read_response(SD_IN_IDLE_STATE)) //查询卡是否处于空闲状态
return SD_RESPONSE_FAILURE;
set_sd_spi_cs_pin(PIN_HIGH);
_spi_read_write_byte(SD_DUMMY_BYTE); //释放卡
if (repeat == 0)
return SD_RESPONSE_FAILURE;
else
return SD_RESPONSE_NO_ERROR;
}
5.4 识别过程
SD nand的识别过程颇为复杂,需要参考下图所示状态机。
其复杂的原因是,随着科技的发展,SD卡也迭代了好几轮,但是协议需要兼容所有版本的卡,因此看上去会复杂很多。
我们采用的SD nand 型号为 CSNPGCR01-AOW,为V2.0.0的卡,且容量为1Gb,因此整体识别路线为中间那条线路。
5.4.1 识别流程说明
V2.0卡识别流程:
1.SD nand上电首先完成初始化,并发送CMD0配置为SPI模式
2.之后发送CMD8命令,读取R7响应,判断SD nand的版本
如果响应值为0x01则判断为V2.0的卡(此时是这个)
如果响应值非0x01则需要进一步判断时V1.0的卡还是MMC卡
3.发送循环指令CMD55+ACMD41,(CMD55用来表示后面的CMD41为ACMD命令),读取R1响应,直到响应0x00表示SD 2.0卡初始化完成
4.发送CMD58命令,读取R3响应,R3中包含OCR寄存器的值,OCR寄存器的第31位(bit30)描述了此卡类型是否为SDHC类型,根据此位判断此卡属于标准容量卡还是高容量卡
V1.0卡识别流程:
1.SD nand上电首先完成初始化,并发送CMD0配置为SPI模式
2.之后发送CMD8命令判断SD nand的版本
如果响应值为0x01则判断为V2.0的卡
如果响应值非0x01则需要进一步判断时V1.0的卡还是MMC卡(此时是这个)
3.发送CMD58命令,并判断响应值R3,如果没有返回则不是SD V1.0的卡
4.发送ACMD41(argument为置0),并判断R1响应值,直到卡空闲
关于CMD8指令,此处重点说明:
CMD8命令的参数中主要包含两个部分,Voltage Supplied(VHS)和check pattern,发送CMD8时,VHS参数应设置为主机支持的电压范围,我们的控制器通常是3.3V,因此此处设置为0001b; check pattern可以设置为任意值,当SD nand(SD卡)接收到此CMD8指令之后会返回R7响应,如果SD nand支持此电压等级,SD nand会回显 VHS 和check pattern的内容在R7中,如果SD nand不支持此电压等级,SD nand将不会返回,并始终保持在空闲状态。
5.4.2 代码实现
SD nand识别代码如下:
SD_ERROR get_sd_card_type(void)
{
uint32_t i = 0;
uint32_t count = 0xFFF;
uint8_t R7R3_Resp[4];
uint8_t R1_Resp;
set_sd_spi_cs_pin(PIN_HIGH);
_spi_read_write_byte(SD_DUMMY_BYTE);
set_sd_spi_cs_pin(PIN_LOW);
sd_send_cmd(SD_CMD_SEND_IF_COND, 0x1AA, 0x87);
/*!< Check if response is got or a timeout is happen */
while (( (R1_Resp = _spi_read_write_byte(SD_DUMMY_BYTE)) == 0xFF) && count) {
count--;
}
if (count == 0) {
/*!< After time out */
return 1;
}
//响应 = 0x05 非V2.0的卡
if(R1_Resp == (SD_IN_IDLE_STATE|SD_ILLEGAL_COMMAND)) {
/*----------Activates the card initialization process-----------*/
count = 0xfff;
do {
set_sd_spi_cs_pin(PIN_HIGH);
_spi_read_write_byte(SD_DUMMY_BYTE);
set_sd_spi_cs_pin(PIN_LOW);
/*!< 发送CMD1完成V1 版本卡的初始化 */
sd_send_cmd(SD_CMD_SEND_OP_COND, 0, 0xFF);
/*!< Wait for no error Response (R1 Format) equal to 0x00 */
if (sd_read_response(SD_RESPONSE_NO_ERROR))
break;
} while (count --);
if (count == 0) {
return 2;
}
SD_Type = SD_TYPE_V1;
//不处理MMC卡
//初始化正常
} else if (R1_Resp == 0x01) { //响应 0x01 V2.0的卡
/*!< 读取CMD8 的R7响应 */
for (i = 0; i < 4; i++) {
R7R3_Resp[i] = _spi_read_write_byte(SD_DUMMY_BYTE);
}
set_sd_spi_cs_pin(PIN_HIGH);
_spi_read_write_byte(SD_DUMMY_BYTE);
set_sd_spi_cs_pin(PIN_LOW);
if(R7R3_Resp[2]==0x01 && R7R3_Resp[3]==0xAA) { //判断该卡是否支持2.7-3.6V电压
count = 200; //支持电压范围,可以操作
do { //发卡初始化指令CMD55+ACMD41
sd_send_cmd(SD_CMD_APP_CMD, 0, 0xFF); //CMD55,以强调下面的是ACMD命令
if (sd_read_response(SD_RESPONSE_NO_ERROR)) // SD_IN_IDLE_STATE
return 3; //超时返回
sd_send_cmd(SD_ACMD_SD_SEND_OP_COND, 0x40000000, 0xFF); //ACMD41命令带HCS检查位
if (sd_read_response(SD_RESPONSE_NO_ERROR))
break;
}while(count--);
if(count == 0)
return 4; //重试次数超时
//初始化指令完成,读取OCR信息,CMD58
//-----------鉴别SDSC SDHC卡类型开始-----------
count = 200;
do {
set_sd_spi_cs_pin(PIN_HIGH);
_spi_read_write_byte(SD_DUMMY_BYTE);
set_sd_spi_cs_pin(PIN_LOW);
sd_send_cmd(SD_CMD_READ_OCR, 0, 0xFF);
if (!sd_read_response(SD_RESPONSE_NO_ERROR))
break;
} while (count--);
if(count == 0)
return 5; //重试次数超时
//响应正常,读取R3响应
/*!< 读取CMD58的R3响应 */
for (i = 0; i < 4; i++) {
R7R3_Resp[i] = _spi_read_write_byte(SD_DUMMY_BYTE);
}
//检查接收到OCR中的bit30(CCS)
//CCS = 0:SDSC CCS = 1:SDHC
if(R7R3_Resp[0]&0x40) { //检查CCS标志 {
SD_Type = SD_TYPE_V2HC;
} else {
SD_Type = SD_TYPE_V2;
}
//-----------鉴别SDSC SDHC版本卡的流程结束-----------
}
}
set_sd_spi_cs_pin(PIN_HIGH);
_spi_read_write_byte(SD_DUMMY_BYTE);
//初始化正常返回
return SD_RESPONSE_NO_ERROR;
}
5.3 数据传输
在完成卡识别之后,便进入了数据传输过程,在输出传输过程内即可完成数据的读写操作。
SD NAND单个块为512字节,擦除、读写都是以块为单位进行的,而且SD NAND可以直接写入,不需要先擦除才能写入!!!牛逼Plus吧!哈哈!
5.3.1 数据写入
数据分为单块写入和多块写入,多块写入可循环执行多块写入实现。单个块写入使用CMD24,多个块写入使用CMD25,注意此处,SD nand的操作与SD卡可能会有所不一样,在对应位置有详细描述。
单块写入步骤如下:
1.发送CMD24,读取响应值R1,判断卡无错误
2.发送写开始指令 0xFE(SD协议中未找到此描述,此应该是SD nand所特有)
3.依次传输写入数据
4.发送两个字节的CRC校验,由于SPI默认没有开启CRC,因此填充为0xFFFF
5.读取卡的状态判断是否有误,结束
5.3.2 数据读取
数据读取也分为单块读取和多块读取,多块读取可采用循环执行单块读取逻辑实现。
单块数据读取步骤如下:
1.发送CMD17,读取响应值R1,判断有无错误2.等待SD nand发送数据输出开始标志 0xFE3.依次读取数据4.多读取两位CRC值,结束6. 总结
综上,便是关于使用SPI接口驱动SD nand的全部说明了,确实花费了不少时间整理说明,关于SD nand的驱动玩法还有很多,比如采用SD接口驱动,移植文件系统,导入日志系统等等,后续有机会有时间我也会继续做整理分享。希望本篇博文能帮助到你对于如何使用SPI实现SD nand的驱动也有大致清晰的了解。相关问答
测控技术与仪器的就业方向都有什么呢?2.能够使用labview编写运动控制系统,并结合运动控制卡,实现对4轴运动的控制,能使用labview实现对数据库ACCESS的读写操作;3.能使用NI公司的视觉系统实现工件...
STM32单片机可以用来做人机交互界面吗?总结,stm32做人机见面还是可以的,当然需要根据具体的项目需求,选择相应的芯片型号,需要学习的知识:GPIO输入输出、ADC数模转换、I2C总线编程、SPI总线编程...按...
学习嵌入式难吗,嵌入式学习路线有哪些?学嵌入式有细分,包括单片机编程、linux驱动编程、linux应用编程、Android应用编程等方向。首先需要明确往那个方面学习发展。单片机学习路线从单片机入门是比...
零基础怎样成为程序员?它由美国贝尔研究所的D.M.Ritchie于1972年推出,1978年后,C语言已先后被移植到大、中、小及微型机上,它可以作为工作系统设计语言,编写系统应用程序,也可以作...
有没有同样爱种花的花友,可以分享一下你的小花园吗?今年春节疫情突发,我除了上班下班外,一直宅在家里看手机,喝酒、睡觉,养成一身赘肉,实在无聊,突发奇想,将窗前一片空地打造一个小花园,半个月下来,初成形...今年...
arm单片机之间有什么区别- 一起装修网一起装修网问答平台为您提供arm单片机之间有什么区别的相关答案,并为您推荐了关于arm单片机之间有什么区别的相关问题,一起装修网问答平台:装修问题,因我而止。
学单片机前,需要首先学好模拟电路、数字电路、C语言这些知识吗?严格来说,学习单片机前需要学好模拟电路、数字电路、C语言这些知识。不过这样的说法会吓住很多人,因为有相当一部分人对模拟电路、数字电路、C语言都不是很熟...
帮个忙各位童鞋 给推荐推荐!!长春靠谱的发电车出租,发电...[回答]植发是运用专利器械从后枕部提取健康的毛囊,按照头发生长的方向自然的移植到脱发部位,当毛囊成活之后,就会长出健康的正常的头发。因为后枕部的毛囊...
诸位行家 跪求答案!!象山值得依赖户外形象标识,户外形象...[回答]形象代言人用英文翻译为imagespeaker。分解解释:1、image英[ˈɪmɪdʒ]美[ˈɪmɪdʒ]n.影像;肖像;概念,意向;镜像,映像vt.反映;想像;作…的像;象...