前回のSTM32G031J6M6マイコンと一緒に
32ビットマイコンが今なら95円。
ということで STM32マイコン STM32L010F4P6 を買ってみた。
これで ストロベリーリナックス I2C低電圧キャラクタ液晶モジュール(16x2行) を動かしてみる。
ピンヘッダをつける
細ピンヘッダとDIP変換基板をはんだ付けする。
データシートを確認する
この辺りは前回と同じなので省略。
STM32とLCDを接続
STM32L010F4P6の
- pin 1 (BOOT0) — GND
- pin 4 (NRST) を I2CLCD のリセットピン(#1)と接続
- pin 5 (VDDA) — VDD(3V3)
- pin 10 (PB9) — 赤色LED(Vf=2V) — 抵抗R(3k) — VDD(3V3)
- pin 15 (VSS) — GND
- pin 16 (VDD) — VDD(3V3)
- pin 17 (I2C1_SDA) を I2CLCD のSDAピン(#3)と接続
- pin 18 (I2C1_SCL) を I2CLCD のSCLピン(#2)と接続
pin 4 (NRST) は GNDとの間にリセット用タクトスイッチを入れて, 抵抗R(3.9k)でプルアップしておく。
pin 17, 18 (SCL, SDA) は抵抗R(3k)でプルアップしておく。
抵抗値に深い意味はない(そこにあったので)。
I2C低電圧キャラクタ液晶モジュール(16x2行)の
- pin 1 (~RST) — STM32L010F4P6 pin#4
- pin 2 (SCL) — STM32L010F4P6 pin#18
- pin 3 (SDA) — STM32L010F4P6 pin#17
- pin 4 (VSS) — GND
- pin 5 (VDD) — VDD(3V3)
ブレッドボードで配線する
ニッケル水素電池(1.2V)を4直列から3端子レギュレータNJU7223F33で3.3V電源を作る。
LCDの下はこうで
これでOK
まだ何も起きない。(あたりまえ)
STM32マイコンにプログラムを書き込まないと。
STM32CubeIDEでプロジェクトを新規作成
File -> New でSTM32 プロジェクトを選択して,
MCU: STM32L010F4を選んで新規作成する。
クロックはHSI RC発振 (16MHz)を4分周して生成した4MHzをSYSCLKにしておきます。
- PA4をGPIO_Output
- I2C1を400kHzで駆動(もちろん100kHzでもいい)
この設定でCubeIDEに生成してもらったmain.cの USER CODE BEGIN ~ END の間をいじる。(それ以外に書いたものは消されるよ)
このままでは前回と同じになるのでC++プロジェクトにします。
なんと, C++14が使えるのを発見したので。
Core/Src/main.c
main.cppがあってもCubeIDEのコード生成が無視するので, エントリポイントはC言語にしておく。
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* <h2><center>© Copyright (c) 2021 STMicroelectronics.
* All rights reserved.</center></h2>
*
* This software component is licensed by ST under BSD 3-Clause license,
* the "License"; You may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
* opensource.org/licenses/BSD-3-Clause
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
I2C_HandleTypeDef hi2c1;
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_I2C1_Init(void);
/* USER CODE BEGIN PFP */
extern void application_setup();
extern void application_loop();
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void) {
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick.
*/
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_I2C1_Init();
/* USER CODE BEGIN 2 */
application_setup();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1) {
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
application_loop();
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void) {
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
/** Configure the main internal regulator output voltage
*/
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_DIV4;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK |
RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK) {
Error_Handler();
}
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_I2C1;
PeriphClkInit.I2c1ClockSelection = RCC_I2C1CLKSOURCE_PCLK1;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) {
Error_Handler();
}
}
/**
* @brief I2C1 Initialization Function
* @param None
* @retval None
*/
static void MX_I2C1_Init(void) {
/* USER CODE BEGIN I2C1_Init 0 */
/* USER CODE END I2C1_Init 0 */
/* USER CODE BEGIN I2C1_Init 1 */
/* USER CODE END I2C1_Init 1 */
hi2c1.Instance = I2C1;
hi2c1.Init.Timing = 0x00000004;
hi2c1.Init.OwnAddress1 = 0;
hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
hi2c1.Init.OwnAddress2 = 0;
hi2c1.Init.OwnAddress2Masks = I2C_OA2_NOMASK;
hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
if (HAL_I2C_Init(&hi2c1) != HAL_OK) {
Error_Handler();
}
/** Configure Analogue filter
*/
if (HAL_I2CEx_ConfigAnalogFilter(&hi2c1, I2C_ANALOGFILTER_ENABLE) != HAL_OK) {
Error_Handler();
}
/** Configure Digital filter
*/
if (HAL_I2CEx_ConfigDigitalFilter(&hi2c1, 0) != HAL_OK) {
Error_Handler();
}
/* USER CODE BEGIN I2C1_Init 2 */
/* USER CODE END I2C1_Init 2 */
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOA_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
/*Configure GPIO pin : PA4 */
GPIO_InitStruct.Pin = GPIO_PIN_4;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void) {
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1) {
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line) {
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line
number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
Core/Src/Application.cpp
C++言語側のエントリポイント。
.cファイルはCコンパイラがコンパイルして作るオブジェクトコード(.o)で
.cppファイルはC++コンパイラがコンパイルして作るオブジェクトコード(.o)で
Cリンケージがどうのこうのとかのリンクエラーがでるのでこのファイルを間に挟むとする。
他にいい方法がわからない。
/*
* Application.cpp
*
* Copyright 2021 Akihiro Yamamoto.
* Licensed under the Apache License, Version 2.0
* <https://spdx.org/licenses/Apache-2.0.html>
*
*/
#include <ST7032iLcd.hpp>
extern I2C_HandleTypeDef hi2c1;
static ST7032iLcd i2c_lcd(hi2c1);
extern "C" void application_setup() {
static const char *lines[] = {u8"Strawberry Linux", u8"I2C エキショウ モジュール"};
i2c_lcd.init();
i2c_lcd.puts(lines[0]);
i2c_lcd.setDdramAddress(0x40);
i2c_lcd.putString(lines[1]);
}
extern "C" void application_loop() {
static ST7032iLcd::IconCode i = 0;
i2c_lcd.showIcon(0x1fff ^ (1 << i));
i = (i + 1) % 13;
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
HAL_Delay(200);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
HAL_Delay(200);
}
Core/Inc/ST7032iLcd.hpp
前回のCソースファイルをC++にしてみた。
/*
* ST7032iLcd.hpp
*
* Copyright 2021 Akihiro Yamamoto.
* Licensed under the Apache License, Version 2.0
* <https://spdx.org/licenses/Apache-2.0.html>
*
*/
#ifndef INC_ST7032ILCD_HPP_
#define INC_ST7032ILCD_HPP_
#include "main.h"
#include <array>
#include <cstdint>
#include <cstring>
#include <string>
#include <vector>
class ST7032iLcd {
public:
//
ST7032iLcd(I2C_HandleTypeDef &h, uint8_t addr = 0x3e)
: i2c(h), i2c_address(addr) {}
//
bool init(uint8_t contrast = 0b101000);
void setContrast(uint8_t contrast);
//
template <std::size_t N>
void sendCommands(const std::array<uint8_t, N> &cmds) {
master_transmit(i2c, i2c_address, I2C_LCD_CBYTE_COMMAND, N, cmds.data());
}
void sendCommands(const std::vector<uint8_t> &cmds) {
master_transmit(i2c, i2c_address, I2C_LCD_CBYTE_COMMAND, cmds.size(),
cmds.data());
}
void sendCommand(uint8_t cmd) {
master_transmit(i2c, i2c_address, I2C_LCD_CBYTE_COMMAND, 1, &cmd);
}
//
template <std::size_t N> void sendData(const std::array<uint8_t, N> &data) {
master_transmit(i2c, i2c_address, I2C_LCD_CBYTE_DATA, N, data.data());
}
void sendData(const std::vector<uint8_t> &data) {
master_transmit(i2c, i2c_address, I2C_LCD_CBYTE_DATA, data.size(),
data.data());
}
void sendDatum(uint8_t datum) {
master_transmit(i2c, i2c_address, I2C_LCD_CBYTE_DATA, 1, &datum);
}
//
void puts(const char *s) {
master_transmit(i2c, i2c_address, I2C_LCD_CBYTE_DATA, std::strlen(s),
reinterpret_cast<const uint8_t *>(s));
}
void putString(const std::string &s);
//
using Command = uint8_t;
const static constexpr Command CmdClearDisplay = 0b00000001;
const static constexpr Command CmdReturnHome = 0b00000010;
//
void setDdramAddress(uint8_t addr) { sendCommand(0x80 | (addr & 0x7f)); }
//
using IconCode = uint16_t;
const static constexpr IconCode IconA = 1 << 12;
const static constexpr IconCode IconB = 1 << 11;
const static constexpr IconCode IconC = 1 << 10;
const static constexpr IconCode IconD = 1 << 9;
const static constexpr IconCode IconE = 1 << 8;
const static constexpr IconCode IconF = 1 << 7;
const static constexpr IconCode IconG = 1 << 6;
const static constexpr IconCode IconH = 1 << 5;
const static constexpr IconCode IconI = 1 << 4;
const static constexpr IconCode IconJ = 1 << 3;
const static constexpr IconCode IconK = 1 << 2;
const static constexpr IconCode IconL = 1 << 1;
const static constexpr IconCode IconM = 1 << 0;
//
void showIcon(IconCode bitflag);
private:
I2C_HandleTypeDef &i2c;
const uint8_t i2c_address;
//
const static constexpr uint8_t LCD_TIMEOUT = 100;
const static constexpr uint8_t LCD_NUM_OF_ROW_CHARACTERS = 16;
//
using CommByte = uint8_t;
const static constexpr CommByte I2C_LCD_CBYTE_COMMAND = 0x00;
const static constexpr CommByte I2C_LCD_CBYTE_DATA = 0x40;
const static constexpr CommByte I2C_LCD_CBYTE_CONTINUATION = 0x80;
//
static void master_transmit(I2C_HandleTypeDef &i2c, uint8_t i2c_address,
CommByte cbyte, size_t size, const uint8_t *data);
};
#endif /* INC_ST7032ILCD_H_ */
Core/Src/ST7032iLcd.cpp
現代のプログラムはコード効率の悪さを気にせずにutf8を使うもんだろ。
ということでutf-8 -> shift-jis半角カタカナ変換を入れてみた。
utf-8の3バイトエンコード部分
https://orange-factory.com/sample/utf8/code3/ef.html#HalfwidthandFullwidthForms
に入っている半角カナをST7032の半角カナにマッピングする。
これは元々HD44780に入っていた半角カナらしいね。
具体的にU+FF61~U+FF9Fを0b10100001~0b11011111にマッピングする。
ツライ。
/*
* ST7032iLcd.cpp
*
* Copyright 2021 Akihiro Yamamoto.
* Licensed under the Apache License, Version 2.0
* <https://spdx.org/licenses/Apache-2.0.html>
*
*/
#include <ST7032iLcd.hpp>
// clang-format off
inline void i2c_lcd_wait_a_moment() {
asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");
asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");
// 10
asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");
asm("NOP");asm("NOP");asm("NOP");asm("NOP");asm("NOP");
// 20
}
// clang-format on
bool ST7032iLcd::init(uint8_t contrast) {
sendCommands({
0b00111000, // function set
0b00111001, // function set
0b00010100, // interval osc
0b01101100, // follower control
});
setContrast(contrast);
HAL_Delay(200);
// second step
sendCommands({
0b00111000, // function set
0b00001100, // Display On
0b00000001, // Clear Display
});
HAL_Delay(2);
return true;
}
void ST7032iLcd::setContrast(uint8_t contrast) {
uint8_t lo = contrast & 0xf;
uint8_t hi = (contrast >> 4) & 0x3;
sendCommands({
static_cast<uint8_t>(0b01110000 | lo), // contrast Low
static_cast<uint8_t>(0b01011100 | hi), // contast High/icon/power
});
}
inline bool Top4bitHigh(uint8_t x) { return (x & 0b11111000) == 0b11110000; }
inline bool Top3bitHigh(uint8_t x) { return (x & 0b11110000) == 0b11100000; }
inline bool Top2bitHigh(uint8_t x) { return (x & 0b11100000) == 0b11000000; }
constexpr static const uint16_t CodePointHankakuKatakanaBegin = 0xff61;
constexpr static const uint16_t CodePointHankakuKatakanaEnd = 0xff9f;
inline bool isHankakuKatakana(uint16_t x) {
return (CodePointHankakuKatakanaBegin <= x &&
x <= CodePointHankakuKatakanaEnd);
}
// utf-8 to ascii & sjis kana
void ST7032iLcd::putString(const std::string &s) {
uint8_t buff[LCD_NUM_OF_ROW_CHARACTERS];
std::size_t buff_idx = 0;
for (std::size_t idx = 0;
buff_idx < LCD_NUM_OF_ROW_CHARACTERS && idx < s.size();) {
if (Top4bitHigh(s[idx])) {
// utf8 4-byte encoded character
// map to '?'
buff[buff_idx++] = '?';
idx += 4;
} else if (Top3bitHigh(s[idx])) {
// utf8 3-byte encoded character
uint8_t top4bit = s[idx + 0] & 0x0f;
uint8_t mid6bit = s[idx + 1] & 0x3f;
uint8_t low6bit = s[idx + 2] & 0x3f;
uint16_t cp = (top4bit << 12) | (mid6bit << 6) | (low6bit << 0);
if (isHankakuKatakana(cp)) {
// Hankaku katakana
buff[buff_idx++] = 0b10100001 + (cp - CodePointHankakuKatakanaBegin);
} else {
// map to '?'
buff[buff_idx++] = '?';
}
idx += 3;
} else if (Top2bitHigh(s[idx])) {
// utf8 2-byte encoded character
// map to '?'
buff[buff_idx++] = '?';
idx += 2;
} else {
// utf8 1-byte encoded character
buff[buff_idx++] = s[idx];
idx += 1;
}
}
master_transmit(i2c, i2c_address, I2C_LCD_CBYTE_DATA, buff_idx, buff);
}
struct Icon {
ST7032iLcd::IconCode icon_code;
uint8_t addr;
uint8_t bit;
};
static const Icon I2C_LCD_ICON_DATA[13] = {
{ST7032iLcd::IconA, 0x00, 0b10000}, // S1 ANTENNA
{ST7032iLcd::IconB, 0x02, 0b10000}, // S11 TEL
{ST7032iLcd::IconC, 0x04, 0b10000}, // S21
{ST7032iLcd::IconD, 0x06, 0b10000}, // S31
{ST7032iLcd::IconE, 0x07, 0b10000}, // S36 UP ARROW
{ST7032iLcd::IconF, 0x07, 0b01000}, // S37 DOWN ARROW
{ST7032iLcd::IconG, 0x09, 0b10000}, // S46 LOCKED
{ST7032iLcd::IconH, 0x0b, 0b10000}, // S56
{ST7032iLcd::IconI, 0x0d, 0b10000}, // S66 BATTERY LOW
{ST7032iLcd::IconJ, 0x0d, 0b01000}, // S67 BATTERY MID
{ST7032iLcd::IconK, 0x0d, 0b00100}, // S68 BATTERY HIGH
{ST7032iLcd::IconL, 0x0d, 0b00010}, // S69 BATTERY FRAME
{ST7032iLcd::IconM, 0x0f, 0b10000}, // S76
};
void ST7032iLcd::showIcon(IconCode bitflag) {
std::array<uint8_t, 16> buff{0};
for (const Icon &i : I2C_LCD_ICON_DATA) {
if (bitflag & i.icon_code) {
buff[i.addr] |= i.bit;
}
}
for (uint8_t i = 0; i < buff.size(); ++i) {
sendCommands({
0b00111001, // function set
static_cast<uint8_t>(0b01000000 | i), // set icon address
});
sendDatum(buff[i]);
}
}
void ST7032iLcd::master_transmit(I2C_HandleTypeDef &i2c, uint8_t i2c_address,
CommByte cbyte, size_t size,
const uint8_t *data) {
if (1 <= size) {
size_t i;
std::vector<uint8_t> buff(size * 2);
for (i = 0; i < (size - 1); ++i) {
buff[i * 2 + 0] = I2C_LCD_CBYTE_CONTINUATION | cbyte;
buff[i * 2 + 1] = data[i];
}
buff[i * 2 + 0] = cbyte;
buff[i * 2 + 1] = data[i];
HAL_I2C_Master_Transmit(&i2c, i2c_address << 1, buff.data(), buff.size(),
LCD_TIMEOUT);
i2c_lcd_wait_a_moment();
}
}
プロジェクトのビルド
Ctrl + B でビルド。
22:39:22 **** Incremental Build of configuration Debug for project hello_stm32l0 ****
make -j16 all
arm-none-eabi-size hello_stm32l0.elf
text data bss dec hex filename
15364 132 1688 17184 4320 hello_stm32l0.elf
Finished building: default.size.stdout
22:39:22 Build Finished. 0 errors, 0 warnings. (took 165ms)
あああ。真っ赤じゃないですか。
このプログラムはSTM32L010F4P6に搭載されている RAM 2キロバイトのうち 1.77キロバイトを,
さらに FLASHメモリ 16キロバイトのうち 15.13キロバイトを使う。
これだけでぱっつんぱっつんになってしまった。
やっぱりC++言語はフットプリントが増えるな。
でも今回はこれしかしないので問題ない。
Nucleoボード付属のST-Link/V2.1でプログラミング
NucleoボードについてるST-Link/V2.1を使います。
こちらはSTM32L010F4P6のピン
NucleoボードのCN2ジャンパーを外して
STM32L010F4P6の
- pin 4 (NRST) を ST-Link のRSTピン(#5)と接続
- pin 15 (VSS) を ST-Link のGNDピン(#3)と接続
- pin 19 (SWDIO) を ST-Link のSWDIOピン(#4)と接続
- pin 20 (SWCLK) を ST-Link のSWCLKピン(#2)と接続
実行にはプログラマーは不要。
GitHubリポジトリ
https://github.com/ak1211/hello_stm32l0/tree/FirstRelease
https://github.com/ak1211/hello_stm32l0/releases/tag/FirstRelease