D15 |
PB7 |
|
1_SDA |
D16 |
PB6 |
|
2_SCL |
void main()
{
init_I2C1();
Delay(50); // вызов отдельной библиотечка для
небольшой
паузы
}
собственно функция инициализации
void init_I2C1(void)
{
// Включаем тактирование нужных модулей
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
// настройка I2C
i2c.I2C_ClockSpeed = 1000000;
i2c.I2C_Mode = I2C_Mode_I2C;
i2c.I2C_DutyCycle = I2C_DutyCycle_2;
i2c.I2C_OwnAddress1 = 0x0;
i2c.I2C_Ack = I2C_Ack_Enable;
i2c.I2C_AcknowledgedAddress =
I2C_AcknowledgedAddress_7bit;
I2C_Init(I2C1, &i2c);
// I2C использует две ноги
микроконтроллера, их тоже нужно настроить
i2c_gpio.GPIO_Pin = GPIO_Pin_6 |
GPIO_Pin_7 ;
i2c_gpio.GPIO_Mode = GPIO_Mode_AF_OD;
i2c_gpio.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &i2c_gpio);
// включаем модуль I2C1
I2C_Cmd(I2C1, ENABLE);
}
Процесс передачи данных по I2C стандартный, генерация стартующей
последовательности, передача данных, генерация завершения/*******************************************************************/
void I2C_StartTransmission(I2C_TypeDef* I2Cx, uint8_t
transmissionDirection, uint8_t slaveAddress)
{
// На всякий случай ждем, пока шина
осовободится
while(I2C_GetFlagStatus(I2Cx,
I2C_FLAG_BUSY));
// Генерируем старт
I2C_GenerateSTART(I2Cx, ENABLE);
// Ждем, пока взлетит нужный флаг
while(!I2C_CheckEvent(I2Cx,
I2C_EVENT_MASTER_MODE_SELECT));
// Посылаем адрес подчиненному
//возможно тут нужен сдвиг влево
//http://microtechnics.ru/stm32-ispolzovanie-i2c/#comment-8109
slaveAddress<<1
I2C_Send7bitAddress(I2Cx, slaveAddress,
transmissionDirection);
// А теперь у нас два варианта развития
событий - в зависимости от выбранного направления обмена данными
if(transmissionDirection==
I2C_Direction_Transmitter)
{
while(!I2C_CheckEvent(I2Cx,
I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
}
if(transmissionDirection==
I2C_Direction_Receiver)
{
while(!I2C_CheckEvent(I2Cx,
I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
}
}
Если неправильно задан адрес, либо нет связи с
устройством, то программа глухо виснет, что не есть правильно, конечно.
/*******************************************************************/
void I2C_WriteData(I2C_TypeDef* I2Cx, uint8_t data)
{
// Просто вызываем готовую функцию из
SPL и ждем, пока данные улетят
I2C_SendData(I2Cx, data);
while(!I2C_CheckEvent(I2Cx,
I2C_EVENT_MASTER_BYTE_TRANSMITTED));
}
Завершение передачи void I2C_EndTransmission(I2C_TypeDef* I2Cx)
{
// Просто вызываем готовую функцию из
SPL для однообразия работы
I2C_GenerateSTOP(I2Cx, ENABLE);
}
#define OLED_COMMAND_MODE
0x80
#define
OLED_DATA_MODE
0x40
#define
OLED_DATA_ADDR
0x78
// передача команды
void LCDI2C_WriteCommand(uint8_t _data){
I2C_StartTransmission (I2C1,
I2C_Direction_Transmitter, OLED_DATA_ADDR);
I2C_WriteData(I2C1, OLED_COMMAND_MODE);
I2C_WriteData(I2C1, (int)(_data));
I2C_EndTransmission(I2C1);
}
// передача данных
void LCDI2C_WriteData(uint8_t _data){
I2C_StartTransmission (I2C1,
I2C_Direction_Transmitter, OLED_DATA_ADDR);
//Wire.beginTransmission(_Addr);
I2C_WriteData(I2C1, OLED_DATA_MODE);
I2C_WriteData(I2C1, (int)(_data));
I2C_EndTransmission(I2C1);
//I2C_GenerateSTOP(I2C1, ENABLE);
//Wire.endTransmission();
}
void LCDI2C_init()
{
/* Init LCD */
LCDI2C_WriteCommand(0xAE); //display off
LCDI2C_WriteCommand(0x20); //Set Memory
Addressing Mode
LCDI2C_WriteCommand(0x10);
//00,Horizontal Addressing Mode;01,Vertical Addressing Mode;10,Page
Addressing Mode (RESET);11,Invalid
LCDI2C_WriteCommand(0xB0); //Set Page
Start Address for Page Addressing Mode,0-7
LCDI2C_WriteCommand(0xC8); //Set COM
Output Scan Direction
LCDI2C_WriteCommand(0x00); //---set low
column address
LCDI2C_WriteCommand(0x10); //---set high
column address
LCDI2C_WriteCommand(0x40); //--set start
line address
LCDI2C_WriteCommand(0x81); //--set
contrast control register
LCDI2C_WriteCommand(0xFF);
LCDI2C_WriteCommand(0xA1); //--set
segment re-map 0 to 127
LCDI2C_WriteCommand(0xA6); //--set
normal display
LCDI2C_WriteCommand(0xA8); //--set
multiplex ratio(1 to 64)
LCDI2C_WriteCommand(0x3F); //
LCDI2C_WriteCommand(0xA4); //0xa4,Output
follows RAM content;0xa5,Output ignores RAM content
LCDI2C_WriteCommand(0xD3); //-set
display offset
LCDI2C_WriteCommand(0x00); //-not offset
LCDI2C_WriteCommand(0xD5); //--set
display clock divide ratio/oscillator frequency
LCDI2C_WriteCommand(0xF0); //--set
divide ratio
LCDI2C_WriteCommand(0xD9); //--set
pre-charge period
LCDI2C_WriteCommand(0x22); //
LCDI2C_WriteCommand(0xDA); //--set com
pins hardware configuration
LCDI2C_WriteCommand(0x12);
LCDI2C_WriteCommand(0xDB); //--set vcomh
LCDI2C_WriteCommand(0x20);
//0x20,0.77xVcc
LCDI2C_WriteCommand(0x8D); //--set DC-DC
enable
LCDI2C_WriteCommand(0x14); //
LCDI2C_WriteCommand(0x2E); // stop
scrolling
LCDI2C_WriteCommand(0xAF); //--turn on
SSD1306 panel вот здесь должен засветиться "мусор"
на экране
}
uint8_t LCD_Buffer[][22] = {
{0x00, 0x00, 0x00, 0x00, 0x00},// (space)
{0x00, 0x00, 0x5F, 0x00, 0x00},// !
{0x00, 0x07, 0x00, 0x07, 0x00},// "
....
{0x00, 0x41, 0x36, 0x08, 0x00},// }
{0x02, 0x01, 0x02, 0x04, 0x02},// ~
{0x08, 0x1C, 0x2A, 0x08, 0x08} //
<-
};
void LCDI2C_draw8x8(uint8_t * buffer, uint8_t x, uint8_t y)
{
// send a bunch of data in one xmission
LCDI2C_WriteCommand(0xB0 + y);//set page
address
//LCDI2C_WriteCommand(x & 0xf);//set lower column address
LCDI2C_WriteCommand(x&
0x8);//set lower column address
LCDI2C_WriteCommand(0x10 | (x
>> 4));//set higher column address
for (x=0; x<8; x++)
{
LCDI2C_WriteData(buffer[x]); // берем
из шрифта 8 байт, которые отвечают за символ и выгоняем в память дисплея
}
}
Помним, что символов у нас помещается на экране 16x8, при этом
небходимо осуществить "прокрукту" строки, если экран закончился.
uint8_t LCD_X,LCD_Y; // это текущие позиции для печати.
Колонок 128, строк 8.
uint8_t LCD_char[16][8]; // текстовый буфер экрана
void LCDI2C_PrintChar(char c) // напечатать символ
с текущей позиции экрана
{
if (c<32|| c>128){
// если переданный символ не входит в таблицу, то
отобразиться пробелом
c=32;
}
// если печатать некуда, сдвигаем экран
if (LCD_Y>7){
// нужна новая строка
int x1 =
0; // прорисовка из буфера
int y1 =
0;
char b;
for
(y1=0;y1<7;y1++){
for (x1 =
0; x1<16;x1++){
b =
LCD_char[x1][y1+1]; // сдвиг на одну строку
if (b<32|| b>127){
b=32;
}
LCD_char[x1][y1]=b;
LCDI2C_draw8x8((uint8_t*)&LCD_Buffer[b-32],x1*8,y1);
}
}
for
(x1=0;x1<16;x1++){ // последняя строка
пробелами заполняется
LCDI2C_draw8x8((uint8_t*)&LCD_Buffer[0],x1*8,7);
LCD_char[x1][7]=32;
}
LCD_Y =
7; // печатаем на последней, но остальные должны быть сдвинуты
}
LCD_char[LCD_X/8][LCD_Y]=c;
// сохраняем напечатанный символ в буфере для сдвига
LCDI2C_draw8x8((uint8_t*)&LCD_Buffer[c-32],LCD_X,LCD_Y);
// печать символа из таблицы символов
LCD_X += 8; // символ состоит
из 8 колонок, следующий печатать через 8
if(LCD_X>=SSD1306_LCDWIDTH)
{
LCD_X
=SSD1306_DEFAULT_SPACE;
LCD_Y++;
// добавить строку
}
}
Вывод на экран идет посимвольно. Если нужно произвести вывод, а
последняя строка заполнена, то экран сдвигается на одну строку вверх.
// печать с переносом, следующая печать с новой строки
void LCDI2C_Printf(char* buf)
{
while (*buf!=0)
{
if((LCD_X>SSD1306_LCDWIDTH)||(LCD_X<5)){LCD_X=SSD1306_DEFAULT_SPACE;}
LCDI2C_PrintChar(*buf++);
}
LCD_Y++;
LCD_X=0;
}
// печать без переноса, но если доходим до 16 символа, то перенос идет
автоматически
void LCDI2C_Print(char* buf)
{
while (*buf!=0)
{
if((LCD_X>SSD1306_LCDWIDTH)||(LCD_X<5)){LCD_X=SSD1306_DEFAULT_SPACE;}
LCDI2C_PrintChar(*buf++);
}
}
Есть возможность сформировать буфер экрана в программе, а затем вывести
его целиком. Это происходит довольно быстро
// обновление экрана из буфера
void LCDI2C_refresh(){
int page = 0;
int column = 0;
LCDI2C_setCursor(0,0);
LCD_X=0;
LCD_Y=0;
for(page=0; page<8; page++) {
LCDI2C_setCursor(0, page);
for(column=0; column<16; column++){
LCDI2C_PrintChar(LCD_char[column][page]);
}
}
}
// очистка экрана, заполняем экран пробелами
void LCDI2C_clear(){
// сначала буфер заполним пробелами
int x1=0;
int y1=0;
for
(y1=0;y1<8;y1++){
for (x1 = 0; x1<16;x1++){
LCD_char[x1][y1]=32;
}
}
LCDI2C_refresh(); //
просто обновим экран
LCD_X=0;
LCD_Y=0;
}
Для заполнения цифрами буфера будет небольшая функция
//{0x00, 0x01, 0x02, 0x04, 0x00},// `
{0xFF, 0xFF, 0xFF, 0xFF, 0xFF},//
` квадратик рисуем вместо апострофа
//
void LCDI2C_bigprint(int col,int num) {
if (num == 0){
LCD_char[col][0] = '`';
LCD_char[col][1] = '`';
LCD_char[col][2] = '`';
LCD_char[col][3] = '`';
LCD_char[col][4] = '`';
LCD_char[col][5] = '`';
LCD_char[col][6] = '`';
LCD_char[col+1][0] = '`';
LCD_char[col+1][1] = ' ';
LCD_char[col+1][2]
= ' ';
LCD_char[col+1][3]
= ' ';
LCD_char[col+1][4]
= ' ';
LCD_char[col+1][5]
= ' ';
LCD_char[col+1][6]
= '`';
LCD_char[col+2][0]= '`';
LCD_char[col+2][1]= '`';
LCD_char[col+2][2]= '`';
LCD_char[col+2][3]= '`';
LCD_char[col+2][4]= '`';
LCD_char[col+2][5]= '`';
LCD_char[col+2][6]= '`';
}
if (num == 1){
...... далее все остальные цифры
}
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_usart.h"
#include "stm32f10x_i2c.h"
#include "stm32f10x_tim.h"
#include "delay.h"
#include "str.h"
#include "I2C.h" // библиотека для работы
с I2C
#include "LCD_I2C.h"
#define TIMER_PRESCALER 1000
// инициализация портов и таймера
GPIO_InitTypeDef PORT;
TIM_TimeBaseInitTypeDef timer;
uint16_t previousState; // по
кнопке будет переключаться
int counter,a0,a1,a2,a3; // для вывода счетчика
void main()
{
uint8_t data;
// светодиод на PB1
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
PORT.GPIO_Pin = GPIO_Pin_1 ;
PORT.GPIO_Mode = GPIO_Mode_Out_PP;
PORT.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init( GPIOB , &PORT);
// кнопка на PB8
PORT.GPIO_Pin = GPIO_Pin_8 ;
PORT.GPIO_Mode = GPIO_Mode_IPD;
PORT.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOB, &PORT);
/*
* GPIO_Mode_AIN — аналоговый вход.
GPIO_Mode_IN_FLOATING — вход без
подтяжки.
GPIO_Mode_IPD — вход с подтяжкой к земле.
GPIO_Mode_IPU — вход с подтяжкой к
питанию.
GPIO_Mode_Out_OD — выход с открытым
коллектором.
GPIO_Mode_Out_PP — выход с подтяжкой.
GPIO_Mode_AF_OD — альтернативный выход с
открытым коллектором.
GPIO_Mode_AF_PP — альтернативный выход с
подтяжкой.
*
*/
//Включаем тактирование порта таймера TIM4
//Таймер 4 у нас висит на шине APB1
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,
ENABLE);
//А тут настройка таймера
//Заполняем поля структуры дефолтными значениями
TIM_TimeBaseStructInit(&timer);
//Выставляем
предделитель
timer.TIM_Prescaler = TIMER_PRESCALER;
//Тут значение,
досчитав до которого таймер сгенерирует прерывание
//Кстати это
значение можно менять в самом прерывании
timer.TIM_Period = 1000;
//Инициализируем
TIM4 нашими значениями
TIM_TimeBaseInit(TIM4, &timer);
__enable_irq(); // разрешить прерывания
init_I2C1(); // инициализация
i2с
Delay(50);
LCDI2C_init(); // инициализация дисплея,
адрес 0x78 там зашит
LCDI2C_clear(); // очистить от прошлого мусора
LCD_X = 0;
LCD_Y = 0;
counter = 0;
//Настраиваем таймер для генерации прерывания по обновлению
(переполнению)
TIM_ITConfig(TIM4, TIM_IT_Update,
ENABLE);
//Запускаем таймер
TIM_Cmd(TIM4, ENABLE);
//Разрешаем соответствующее прерывание
NVIC_EnableIRQ(TIM4_IRQn);
while(1)
{
// Вся полезная работа – в прерывании
if(GPIO_ReadInputDataBit (GPIOB, GPIO_Pin_8)){ // нажата
кнопка
NVIC_DisableIRQ(TIM4_IRQn); // выключили прерывание
LCDI2C_clear();
GPIO_SetBits(GPIOB, GPIO_Pin_1);
if (previousState==0){
previousState = 1;
GPIO_SetBits(GPIOB, GPIO_Pin_1);
}else {
previousState = 0;
GPIO_ResetBits(GPIOB, GPIO_Pin_1);
}
Delay(500);
NVIC_EnableIRQ(TIM4_IRQn); // опять включили
}
__NOP();
}
return;
}
void TIM4_IRQHandler() // прерывание от
таймера
{
// перебрасываем счетчики
counter++;
if (counter>9999){
counter=0;
}
char buf[5];
itoa(counter,buf,10); // теперь в строке символы,
функция в библиотечке
if (previousState !=1 ) // по нажатию
кнопки меняется представление на экране
{ // большие цифры
GPIO_SetBits(GPIOB, GPIO_Pin_1); //
включили светодиод
// печать
больших цифр
// от
0000 до 9999
a0 = 0;
a1 = 0;
a2 = 0;
a3 = 0;
if
(strlen(buf)==1){
a0= buf[0]-48;
}
if
(strlen(buf)==2){
a0 = buf[1]-48;
a1 = buf[0]-48;
}
if
(strlen(buf)==3){
a0 = buf[2]-48;
a1 = buf[1]-48;
a2 = buf[0]-48;
}
if
(strlen(buf)==4){
a0 = buf[3]-48;
a1 = buf[2]-48;
a2 = buf[1]-48;
a3 = buf[0]-48;
}
LCDI2C_bigprint(0,a3); // заполняем большой цифрой
буфер
LCDI2C_bigprint(4,a2);
LCDI2C_bigprint(9,a1);
LCDI2C_bigprint(13,a0);
LCDI2C_refresh();
timer.TIM_Period = 1000;
TIM_TimeBaseInit(TIM4, &timer);
}
else
{
//строки с прокруткой экрана
//гасим светодиод
GPIO_ResetBits(GPIOB, GPIO_Pin_1);
LCDI2C_Print("test: "); //
печать без переноса строки
LCDI2C_Printf(buf);
// печать с переносом строки
timer.TIM_Period =
20000; // медленный пересчет циферок в строковом
режиме
TIM_TimeBaseInit(TIM4, &timer);
}
//Очищаем бит прерывания
TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
}