【程序】STM32使用SPI接口操作93C46存储器(非软件模拟)

/* 93C46选默认的16位模式 */
#include <stm32f10x.h>

#define _BV(n) (1 << (n))
#define CS_0 (GPIOA->BRR = GPIO_BRR_BR3)
#define CS_1 (GPIOA->BSRR = GPIO_BSRR_BS3)

uint8_t id = 0;
uint16_t num = 0;
const uint8_t seg8[] = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90};

void delay(void)
{
	uint16_t i;
	for (i = 0; i < 20000; i++);
}

void delay_short(void)
{
	uint8_t i;
	for (i = 0; i < 250; i++);
}

void ser_in(uint8_t data)
{
	uint8_t i;
	for (i = 0; i < 8; i++)
	{
		GPIOB->BRR = GPIO_BRR_BR9; // SCLK=>PB9
		if (data & 0x80)
			GPIOB->BSRR = GPIO_BSRR_BS7; // DIO=>PB7
		else
			GPIOB->BRR = GPIO_BRR_BR7;
		data <<= 1;
		GPIOB->BSRR = GPIO_BSRR_BS9;
	}
}

void par_out(void)
{
	GPIOB->BRR = GPIO_BRR_BR8; // RCLK=>PB8
	GPIOB->BSRR = GPIO_BSRR_BS8;
}

void seg_scan(void)
{
	uint8_t i;
	uint32_t n = num;
	for (i = 0; i <= 4; i++)
	{
		ser_in(seg8[n % 10]);
		ser_in(_BV(i));
		par_out();
		delay();
		n /= 10;
	}
	
	n = id;
	for (i = 6; i <= 7; i++)
	{
		ser_in(seg8[n % 10]);
		ser_in(_BV(i));
		par_out();
		delay();
		n /= 10;
	}
}

// 读取单个存储单元
uint16_t _93C46_Read(uint8_t addr)
{
	// SPI中我们配置的是CPOL=0, 即SCK的空闲状态为低电平; CPHA=0, 也就是在SCK的上升沿对数据进行采样
	// 这里会产生一个问题: 根据EEPROM手册的时序图Figure 2, 虽然发送数据没有问题, 但接收数据时,  SCK上升沿后需要等待tPD0或tPD1的时间后本位的数据才会出现在DO上
	// 如果上升沿出现时就抓取数据, 那么读到的不是本位的数据,而是上一位的数据
	// 因此,我们接收到的数据都是右移了一位之后的数据
	uint16_t data = 0;
	uint16_t temp;
	
	// 开始
	CS_1;
	temp = SPI1->DR; // 清空接收缓冲(若不及时清除将无法收到新数据)
	temp = SPI1->SR; // 清OVR标志
	SPI1->CR1 |= SPI_CR1_SPE; // 启用SPI
	
	SPI1->DR = 0xc000 | ((addr & 0x3f) << 7); // 发送操作码(110)、地址码 (1)
	while ((SPI1->SR & SPI_SR_TXE) == 0); // 注意: TXE=1并不代表当前字节发送完毕, 有可能只发送了一两个字节
	SPI1->DR = 0x0000; // 送入下次要发的内容: 根据器件手册上的时序图, 地址发送完毕后应发送0x0000, 即DI一直为低电平,不是什么都不发 (2)
	while ((SPI1->SR & SPI_SR_RXNE) == 0); // 等待接收数据
	temp = SPI1->DR; // 收到的数据: 第15~7位全为1(从器件发送的高阻态被视为1), 第6位为0(dummy bit, 空白位), 第5~0位为所读取数据的第15~10位
	// 数据的发送和接收是同时进行的
	// 只有当前字节发送完毕了, RXNE才置位, 而TXE早就置位了(参阅手册上的Figure 240)
	// RXNE置位表明(1)已发送完毕, 开始发送(2)
	data = (temp & 0x3f) << 10; // 去掉第15~6位后送入data变量
	
	while ((SPI1->SR & SPI_SR_RXNE) == 0); // 等待(2)发送完毕
	temp = SPI1->DR; // 第9~0位数据
	data |= temp >> 6;
	
	// 结束
	while ((SPI1->SR & SPI_SR_TXE) == 0);
	while (SPI1->SR & SPI_SR_BSY);
	SPI1->CR1 &= ~SPI_CR1_SPE; // 关闭SPI
	CS_0;
	delay_short();
	return data;
}

// 等待操作完毕
void _93C46_Wait(void)
{
	uint16_t temp;
	delay_short();
	CS_1;
	temp = SPI1->DR; // 清空接收缓冲
	temp = SPI1->SR; // 清OVR标志
	SPI1->CR1 |= SPI_CR1_RXONLY; // 设为仅接收
	delay_short(); // tSV<=0.25us
	SPI1->CR1 |= SPI_CR1_SPE; // 开始接收
	// 注: 开始位SB=0表示状态检测, SB=1表示执行指令
	
	do
	{
		while ((SPI1->SR & SPI_SR_RXNE) == 0);
		temp = SPI1->DR;
	} while (temp != 0xffff); // 只要收到的数据含有0, 就继续等待
	
	SPI1->CR1 &= ~SPI_CR1_SPE;
	SPI1->CR1 &= ~SPI_CR1_RXONLY;
	CS_0;
	delay_short();
}

// 允许/禁止擦写
// 该命令没有位数要求, 可以在末尾添很多0
void _93C46_EnableWrite(uint8_t enabled)
{
	CS_1;
	SPI1->CR1 |= SPI_CR1_SPE;
	SPI1->DR = (enabled) ? 0x9800 : 0x8000; // 操作码: 100, 地址: 110000或000000
	while ((SPI1->SR & SPI_SR_TXE) == 0);
	while (SPI1->SR & SPI_SR_BSY);
	SPI1->CR1 &= ~SPI_CR1_SPE;
	CS_0;
	delay_short(); // tCS>=0.25us
}

// 写入单个存储单元
// 93C46要求发送的总位数必须为25位, 多一位少一位都不能写入成功
// 所以这时必须采用一定的技巧
void _93C46_Write(uint8_t addr, uint16_t data)
{
	CS_1;
	SPI1->CR1 &= ~SPI_CR1_DFF; // SPI改为8位传送模式 (但93C46仍为16位模式, 因为ORG引脚电平没变)
	SPI1->CR1 |= SPI_CR1_SPE; // 开启SPI接口
	if (addr != 0x80)
		SPI1->DR = 0xa0 | ((addr & 0x3f) >> 1); // 发送操作码(101)和地址码前5位 (1)
	else
		SPI1->DR = 0x88;
	while ((SPI1->SR & SPI_SR_TXE) == 0);
	SPI1->DR = ((addr << 7) | (data >> 9)) & 0xff; // 发送地址码最后一位和第15~9位数据 (2)
	while ((SPI1->SR & SPI_SR_TXE) == 0);
	SPI1->DR = (data >> 1) & 0xff; // 发送第8~1位数据 (3)
	while ((SPI1->SR & SPI_SR_TXE) == 0);
	SPI1->DR = (data << 7) & 0x80; // 发送最后一位数据 (4)
	while ((SPI1->SR & SPI_SR_TXE) == 0); // 当TXE置位时,(4)的最高位刚好发送完毕
	CS_0; // 此时立即使片选信号CS无效
	while (SPI1->SR & SPI_SR_BSY); // 等待SPI接口把(4)中剩下的7位发送完, 因为此时片选信号CS无效, 所以93C46接收不到
	SPI1->CR1 &= ~SPI_CR1_SPE; // 关闭SPI接口
	SPI1->CR1 |= SPI_CR1_DFF; // 改回16位模式
	_93C46_Wait();
	
	// 技巧2: 如果最后一次发送的是两位而不是1位, 只需要在TXE-while后面加上一些空操作nop延时,然后将CS置0
}

// 擦除单个存储单元
// EEPROM要求发送的位数必须为3+6=9位, 最后一位采取提前关闭CS的方法发送
void _93C46_Erase(uint8_t addr)
{
	CS_1;
	SPI1->CR1 &= ~SPI_CR1_DFF; // 8位SPI模式
	SPI1->CR1 |= SPI_CR1_SPE;
	if (addr != 0x80)
		SPI1->DR = 0xe0 | ((addr & 0x3f) >> 1); // 操作码和地址码前5位 (1)
	else
		SPI1->DR = 0x90;
	while ((SPI1->SR & SPI_SR_TXE) == 0);
	SPI1->DR = addr << 7; // 地址码最后一位 (2)
	while ((SPI1->SR & SPI_SR_TXE) == 0);
	CS_0; // 当(2)的最高位刚发完时, 立即关闭片选信号
	while (SPI1->SR & SPI_SR_BSY); // 等待(2)发送完毕
	SPI1->CR1 &= ~SPI_CR1_SPE;
	SPI1->CR1 |= SPI_CR1_DFF;
	_93C46_Wait();
}

// 擦除所有存储单元
// 经测试,该命令可以在3.2V的电压下完成
// 本人使用的是ST公司的93C46芯片,和STM32单片机是同一家公司生产的
void _93C46_EraseAll(void)
{
	_93C46_Erase(0x80);
}

// 将所有的存储单元设为指定值
// 同样也可以在3.2V的电压下完成
void _93C46_WriteAll(uint16_t dat)
{
	_93C46_Write(0x80, dat);
}

int main(void)
{
	uint8_t i;
	RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_SPI1EN;
	
	// CS(1)接PA3, SCK=PA5接SK(2), MISO=PA6接DO(4), MOSI=PA7接DI(3), ORG悬空选16位模式
	// 根据参考手册RM0008_166页的Table25,SCK、MOSI应配置为复用推挽输出(b),而MISO应配置为带上拉输入(8)
	GPIOA->CRL = 0xb8b03000;
	GPIOA->BSRR = GPIO_BSRR_BS6; // 带上拉输入
	
	// 数码管动态扫描端口PB7~PB9
	GPIOB->CRH = 0x00000033;
	GPIOB->CRL = 0x30000000;
	
	SPI1->CR1 |= SPI_CR1_MSTR; // 设为主模式
	SPI1->CR1 |= SPI_CR1_DFF; // 每次传送的数据位数为16位(DFF=1)
	SPI1->CR1 |= SPI_CR1_BR; // BR=111, 选256分频
	
	// SPI1->CR2 &= ~SPI_CR2_SSOE; // 不使用NSS(=PA4)端口。因为该端口的有效电平是低电平, 而93C46的有效片选信号为高电平
	SPI1->CR1 |= SPI_CR1_SSM; // 使用软件管理NSS端口,PA4可用作普通I/O口
	SPI1->CR1 |= SPI_CR1_SSI; // 设置NSS的状态: 已选中
	
	/*
	_93C46_EnableWrite(1); // 允许擦写
	_93C46_Write(2, 123);
	_93C46_Write(3, 456);
	_93C46_Write(8, 789);
	for (i = 0; i < 64; i++)
		_93C46_Write(i, 50000 + i * 100 + i);
	
	_93C46_EnableWrite(0); // 禁止擦写
	_93C46_EraseAll();
	_93C46_WriteAll(43);
	_93C46_Erase(8);
	*/
	
	while (1)
	{
		num = _93C46_Read(id);
		for (i = 0; i < 50; i++)
			seg_scan();
		id++;
		if (id > 63)
			id = 0;
	}
}

所用的单片机:STM32F103C8T6

电源电压:3.2V

  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

巨大八爪鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值