/* 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;
}
}